mirror of
https://github.com/nexu-io/open-design.git
synced 2026-05-31 19:04:39 +07:00
feat(skills): integrate lewislulu/html-ppt-skill + 15 per-template Examples cards (#193)
* feat(skills): integrate lewislulu/html-ppt-skill as html-ppt + 15 per-template Examples cards
Bring the MIT-licensed lewislulu/html-ppt-skill upstream into skills/html-ppt/
with its full asset tree (36 themes, 31 single-page layouts, 27 CSS + 20
canvas-FX animations, runtime + presenter mode, all 15 full-deck templates,
and the upstream LICENSE preserved verbatim).
Surface each full-deck template as its own Examples gallery card via thin
wrapper skills under skills/html-ppt-<template>/. Each wrapper ships:
- SKILL.md with `od.mode=deck`, scenario, `featured: 20-34` (slotting after
the existing curated cards), an `od.example_prompt` tuned to the template,
and `od.upstream` pointing at the upstream repo. Clicking "Use this prompt"
on a card now wires up `kind=deck` + `speakerNotes=true` and seeds the
composer with the upstream's authoring flow so the prompt -> output path
matches the upstream demo.
- example.html baked self-contained (fonts/base/animations/style/theme CSS
inlined, runtime <script> stripped) so the gallery srcdoc iframe renders
the upstream look without external paths.
scripts/scaffold-html-ppt-skills.mjs and scripts/bake-html-ppt-examples.mjs
are idempotent generators — re-run after editing skills/html-ppt/ to re-sync
all per-template wrappers and their baked examples.
Add a Credits section + extend the License section in README.md /
README.zh-CN.md / README.ko.md to credit the upstream alongside the
already-cited op7418/guizang-ppt-skill.
* fix(scripts): allowlist html-ppt skill JS for residual-js check
Add scripts/bake-html-ppt-examples.mjs and scripts/scaffold-html-ppt-skills.mjs
to allowedExactPaths, and skills/html-ppt/assets/ to allowedPathPrefixes so
pnpm check:residual-js no longer flags the vendored upstream runtime JS or the
new maintainer-only .mjs scripts.
* fix(skills): keep all slides in baked html-ppt examples + correct asset guidance
The bake script's `STATIC_FALLBACK_CSS` set `.slide+.slide{display:none}`,
which silently truncated every baked `example.html` to slide 1. That artifact
is also served by `/api/skills/:id/example` and reused by the Examples
preview modal's share/export and print-to-PDF, so the rule dropped the rest
of the deck from those flows. Drop the rule — slides now stack in the
print-style flow the surrounding comment already described, the gallery
thumbnail iframe still naturally lands on slide 1 (each `.slide` is `100vh`),
and modal/share/export contains the full deck.
The wrapper SKILL.md authoring instructions told agents to copy
`index.html` + `style.css` into a project while keeping the upstream
`../../../assets/...` links, but those parent-relative URLs only resolve
in-tree (the template sits three folders deep). Once the file lives in a
project artifact, `base.css`, `animations.css`, and `runtime.js` 404 and
the deck never activates. Replace step 3 with two recipes — copy the
shared assets into a project-local `assets/` and rewrite the four tags,
or inline the CSS/JS directly — and re-emit all 15 wrapper SKILL.md
files via the scaffold generator.
This commit is contained in:
parent
1edab990bb
commit
f4ab52d9dd
261 changed files with 18793 additions and 3 deletions
|
|
@ -647,6 +647,12 @@ Open Design을 앞으로 나아가게 도와준 모든 분께 감사드립니다
|
|||
|
||||
곡선이 위로 휘면 — 그것이 우리가 찾는 신호입니다. ★를 눌러 위로 밀어주세요.
|
||||
|
||||
## 크레딧 / Credits
|
||||
|
||||
마스터 [`skills/html-ppt/`](skills/html-ppt/) skill과 [`skills/html-ppt-*/`](skills/) 아래의 15개 per-template wrapper(15개 full-deck 템플릿, 36개 테마, 31개 single-page 레이아웃, 27개 CSS 애니메이션 + 20개 canvas FX, 키보드 runtime, 자석식 카드 presenter mode 포함)는 오픈소스 프로젝트 [`lewislulu/html-ppt-skill`](https://github.com/lewislulu/html-ppt-skill)(MIT)에서 통합되었습니다. 원본 LICENSE는 [`skills/html-ppt/LICENSE`](skills/html-ppt/LICENSE)에 보존되어 있고 저작권 표시는 [@lewislulu](https://github.com/lewislulu)에게 있습니다. 각 per-template Examples 카드(`html-ppt-pitch-deck`, `html-ppt-tech-sharing`, `html-ppt-presenter-mode`, `html-ppt-xhs-post` …)는 authoring 가이드를 마스터 skill에 위임하므로, **Use this prompt** 클릭 시 업스트림과 동일한 prompt → 출력 동작이 그대로 보존됩니다.
|
||||
|
||||
[`skills/guizang-ppt/`](skills/guizang-ppt/) 매거진/가로 스와이프 deck flow는 [`op7418/guizang-ppt-skill`](https://github.com/op7418/guizang-ppt-skill)(MIT)에서 통합되었으며, 저작권 표시는 [@op7418](https://github.com/op7418)에게 있습니다.
|
||||
|
||||
## 라이선스
|
||||
|
||||
Apache-2.0. 번들된 `skills/guizang-ppt/`는 원래 [LICENSE](skills/guizang-ppt/LICENSE)(MIT)와 [op7418](https://github.com/op7418)에 대한 저작권 표시를 유지합니다.
|
||||
Apache-2.0. 번들된 `skills/guizang-ppt/`는 원래 [LICENSE](skills/guizang-ppt/LICENSE)(MIT)와 [op7418](https://github.com/op7418)에 대한 저작권 표시를 유지합니다. 번들된 `skills/html-ppt/`는 원래 [LICENSE](skills/html-ppt/LICENSE)(MIT)와 [lewislulu](https://github.com/lewislulu)에 대한 저작권 표시를 유지합니다.
|
||||
|
|
|
|||
|
|
@ -651,6 +651,12 @@ The SVG above is regenerated daily by [`.github/workflows/metrics.yml`](.github/
|
|||
|
||||
If the curve bends up, that's the signal we look for. ★ this repo to push it.
|
||||
|
||||
## Credits
|
||||
|
||||
The HTML PPT Studio family of skills — the master [`skills/html-ppt/`](skills/html-ppt/) and the per-template wrappers under [`skills/html-ppt-*/`](skills/) (15 full-deck templates, 36 themes, 31 single-page layouts, 27 CSS animations + 20 canvas FX, the keyboard runtime, and the magnetic-card presenter mode) — are integrated from the open-source project [`lewislulu/html-ppt-skill`](https://github.com/lewislulu/html-ppt-skill) (MIT). The upstream LICENSE ships in-tree at [`skills/html-ppt/LICENSE`](skills/html-ppt/LICENSE) and authorship credit goes to [@lewislulu](https://github.com/lewislulu). Each per-template Examples card (`html-ppt-pitch-deck`, `html-ppt-tech-sharing`, `html-ppt-presenter-mode`, `html-ppt-xhs-post`, …) delegates authoring guidance to the master skill so the upstream's prompt → output behavior is preserved end-to-end when you click **Use this prompt**.
|
||||
|
||||
The magazine / horizontal-swipe deck flow under [`skills/guizang-ppt/`](skills/guizang-ppt/) is integrated from [`op7418/guizang-ppt-skill`](https://github.com/op7418/guizang-ppt-skill) (MIT). Authorship credit goes to [@op7418](https://github.com/op7418).
|
||||
|
||||
## License
|
||||
|
||||
Apache-2.0. The bundled `skills/guizang-ppt/` retains its original [LICENSE](skills/guizang-ppt/LICENSE) (MIT) and authorship attribution to [op7418](https://github.com/op7418).
|
||||
Apache-2.0. The bundled `skills/guizang-ppt/` retains its original [LICENSE](skills/guizang-ppt/LICENSE) (MIT) and authorship attribution to [op7418](https://github.com/op7418). The bundled `skills/html-ppt/` retains its original [LICENSE](skills/html-ppt/LICENSE) (MIT) and authorship attribution to [lewislulu](https://github.com/lewislulu).
|
||||
|
|
|
|||
|
|
@ -645,6 +645,12 @@ Daemon 启动时从 `PATH` 自动检测,无需配置。流式分发逻辑在 [
|
|||
|
||||
曲线往上走 —— 那就是我们想看到的信号。点 ★ 推它一把。
|
||||
|
||||
## 鸣谢 / Credits
|
||||
|
||||
[`skills/html-ppt/`](skills/html-ppt/) 主 skill 以及 [`skills/html-ppt-*/`](skills/) 下的 15 个 per-template 子 skill —— 含 15 套 full-deck、36 套主题、31 个单页 layout、27 个 CSS 动画 + 20 个 canvas FX、键盘 runtime 与磁吸卡片演讲者模式 —— 整合自开源项目 [`lewislulu/html-ppt-skill`](https://github.com/lewislulu/html-ppt-skill)(MIT)。原始 LICENSE 已保留于 [`skills/html-ppt/LICENSE`](skills/html-ppt/LICENSE),原作者归属 [@lewislulu](https://github.com/lewislulu)。每张 per-template 的 Examples 卡片(`html-ppt-pitch-deck`、`html-ppt-tech-sharing`、`html-ppt-presenter-mode`、`html-ppt-xhs-post` …)都把 authoring 指南委托给主 skill,所以点 **Use this prompt** 后,沿用上游同样的 prompt → 产物路径。
|
||||
|
||||
[`skills/guizang-ppt/`](skills/guizang-ppt/) 杂志风横向翻页 deck 整合自 [`op7418/guizang-ppt-skill`](https://github.com/op7418/guizang-ppt-skill)(MIT),原作者归属 [@op7418](https://github.com/op7418)。
|
||||
|
||||
## License
|
||||
|
||||
Apache-2.0。内置的 [`skills/guizang-ppt/`](skills/guizang-ppt/) 保留它原始的 [LICENSE](skills/guizang-ppt/LICENSE)(MIT)和原作者 [op7418](https://github.com/op7418) 的归属。
|
||||
Apache-2.0。内置的 [`skills/guizang-ppt/`](skills/guizang-ppt/) 保留它原始的 [LICENSE](skills/guizang-ppt/LICENSE)(MIT)和原作者 [op7418](https://github.com/op7418) 的归属。内置的 [`skills/html-ppt/`](skills/html-ppt/) 保留它原始的 [LICENSE](skills/html-ppt/LICENSE)(MIT)和原作者 [lewislulu](https://github.com/lewislulu) 的归属。
|
||||
|
|
|
|||
133
scripts/bake-html-ppt-examples.mjs
Normal file
133
scripts/bake-html-ppt-examples.mjs
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
#!/usr/bin/env node
|
||||
// Bake self-contained example.html files for each html-ppt full-deck template.
|
||||
//
|
||||
// The Examples gallery in apps/web renders each skill's example via an iframe
|
||||
// `srcdoc`, which has no opener path and can't reach companion CSS files.
|
||||
// The upstream `templates/full-decks/<name>/index.html` references shared
|
||||
// assets via `../../../assets/...` paths — we inline those + the per-deck
|
||||
// `style.css`, drop the runtime <script> (the gallery only shows a static
|
||||
// snapshot of slide 1), and write the result to:
|
||||
//
|
||||
// skills/html-ppt-<name>/example.html
|
||||
//
|
||||
// Each per-template skill folder must already exist with a SKILL.md.
|
||||
|
||||
import { readFile, writeFile, readdir, mkdir } from 'node:fs/promises';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
||||
const HTML_PPT = path.join(ROOT, 'skills', 'html-ppt');
|
||||
const ASSETS = path.join(HTML_PPT, 'assets');
|
||||
const FULL_DECKS = path.join(HTML_PPT, 'templates', 'full-decks');
|
||||
const SKILLS = path.join(ROOT, 'skills');
|
||||
|
||||
async function readMaybe(p) {
|
||||
try {
|
||||
return await readFile(p, 'utf8');
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
const sharedFonts = await readMaybe(path.join(ASSETS, 'fonts.css'));
|
||||
const sharedBase = await readMaybe(path.join(ASSETS, 'base.css'));
|
||||
const sharedAnimations = await readMaybe(
|
||||
path.join(ASSETS, 'animations', 'animations.css'),
|
||||
);
|
||||
|
||||
// Without runtime.js, no slide gets `.is-active`, so the deck would render
|
||||
// blank. For a static preview we surface every slide and stack them in
|
||||
// print-style flow: each slide is 100vh, so the gallery thumbnail iframe
|
||||
// (fixed viewport) naturally lands on slide 1, while the modal/export and
|
||||
// print-to-PDF flows scroll/page through the full deck. We deliberately do
|
||||
// not hide later slides — this artifact is also served via
|
||||
// `/api/skills/:id/example` and reused by share/export, where dropping
|
||||
// everything past slide 1 would silently truncate the deck.
|
||||
const STATIC_FALLBACK_CSS = `
|
||||
/* Static-preview fallback (runtime.js is absent — keep every slide visible) */
|
||||
.deck{height:auto;min-height:100vh;overflow:visible}
|
||||
.slide{position:relative;inset:auto;opacity:1;pointer-events:auto;transform:none;height:100vh;page-break-after:always}
|
||||
.deck-header,.deck-footer,.slide-number,.progress-bar,.notes-overlay,.overview{pointer-events:none}
|
||||
.notes{display:none!important}
|
||||
`;
|
||||
|
||||
function inlineLink(html, href, content) {
|
||||
// Replace <link rel="stylesheet" href="..."> regardless of attribute order.
|
||||
const re = new RegExp(
|
||||
`<link[^>]*href=["']${href.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}["'][^>]*>`,
|
||||
'g',
|
||||
);
|
||||
return html.replace(re, `<style>${content}\n</style>`);
|
||||
}
|
||||
|
||||
async function bakeOne(name) {
|
||||
const indexPath = path.join(FULL_DECKS, name, 'index.html');
|
||||
const stylePath = path.join(FULL_DECKS, name, 'style.css');
|
||||
let html = await readMaybe(indexPath);
|
||||
if (!html) {
|
||||
console.warn(`[bake] missing ${indexPath}`);
|
||||
return false;
|
||||
}
|
||||
const style = await readMaybe(stylePath);
|
||||
|
||||
html = inlineLink(html, '../../../assets/fonts.css', sharedFonts);
|
||||
html = inlineLink(html, '../../../assets/base.css', sharedBase);
|
||||
html = inlineLink(
|
||||
html,
|
||||
'../../../assets/animations/animations.css',
|
||||
sharedAnimations,
|
||||
);
|
||||
html = inlineLink(html, 'style.css', style);
|
||||
|
||||
// Some templates ship a `<link id="theme-link" href="../../../assets/themes/<theme>.css">`
|
||||
// so the runtime can cycle themes via `T`. The static gallery has no runtime
|
||||
// and srcdoc can't follow `../../../`, so inline whatever theme the template
|
||||
// shipped with — that's the look the upstream README screenshots show.
|
||||
html = html.replace(
|
||||
/<link[^>]*href=["']\.\.\/\.\.\/\.\.\/assets\/themes\/([\w-]+)\.css["'][^>]*>/g,
|
||||
(_match, themeName) => {
|
||||
try {
|
||||
const css = readFileSync(
|
||||
path.join(ASSETS, 'themes', `${themeName}.css`),
|
||||
'utf8',
|
||||
);
|
||||
return `<style data-theme="${themeName}">${css}\n</style>`;
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Drop the runtime + any FX runtime references — the static gallery only
|
||||
// shows slide 1 and these scripts would 404 inside the srcdoc sandbox.
|
||||
html = html.replace(
|
||||
/<script[^>]*src=["'][^"']*runtime\.js["'][^>]*><\/script>/g,
|
||||
'',
|
||||
);
|
||||
html = html.replace(
|
||||
/<script[^>]*src=["'][^"']*fx-runtime\.js["'][^>]*><\/script>/g,
|
||||
'',
|
||||
);
|
||||
|
||||
// Append the static fallback at the very end of <head> so it overrides
|
||||
// base.css's `.slide{opacity:0}`. We append rather than prepend to win
|
||||
// specificity ties without bumping selectors.
|
||||
html = html.replace(/<\/head>/i, `<style>${STATIC_FALLBACK_CSS}</style></head>`);
|
||||
|
||||
const outDir = path.join(SKILLS, `html-ppt-${name}`);
|
||||
await mkdir(outDir, { recursive: true });
|
||||
await writeFile(path.join(outDir, 'example.html'), html, 'utf8');
|
||||
return true;
|
||||
}
|
||||
|
||||
const entries = await readdir(FULL_DECKS, { withFileTypes: true });
|
||||
const names = entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
||||
|
||||
let baked = 0;
|
||||
for (const name of names) {
|
||||
if (await bakeOne(name)) baked++;
|
||||
}
|
||||
console.log(`[bake] wrote ${baked}/${names.length} example.html files`);
|
||||
console.log(`[bake] templates: ${names.join(', ')}`);
|
||||
|
|
@ -31,6 +31,8 @@ const allowedExactPaths = new Set([
|
|||
"scripts/import-prompt-templates.mjs",
|
||||
"scripts/postinstall.mjs",
|
||||
"apps/packaged/esbuild.config.mjs",
|
||||
"scripts/bake-html-ppt-examples.mjs",
|
||||
"scripts/scaffold-html-ppt-skills.mjs",
|
||||
"scripts/sync-hyperframes-skill.mjs",
|
||||
"scripts/verify-media-models.mjs",
|
||||
"tools/dev/bin/tools-dev.mjs",
|
||||
|
|
@ -51,6 +53,8 @@ const allowedPathPrefixes = [
|
|||
"e2e/reports/test-results/",
|
||||
// Vendored upstream HyperFrames skill helper scripts.
|
||||
"skills/hyperframes/scripts/",
|
||||
// Vendored upstream html-ppt skill runtime assets (lewislulu/html-ppt-skill).
|
||||
"skills/html-ppt/assets/",
|
||||
"test-results/",
|
||||
"vendor/",
|
||||
];
|
||||
|
|
|
|||
297
scripts/scaffold-html-ppt-skills.mjs
Normal file
297
scripts/scaffold-html-ppt-skills.mjs
Normal file
|
|
@ -0,0 +1,297 @@
|
|||
#!/usr/bin/env node
|
||||
// Scaffold one Open Design skill per upstream html-ppt full-deck template.
|
||||
//
|
||||
// Each generated `skills/html-ppt-<name>/SKILL.md` ships only frontmatter +
|
||||
// a short body. Authoring guidance, layouts, themes, and animations live in
|
||||
// the master `skills/html-ppt/` skill — these wrappers only exist so each
|
||||
// template surfaces as its own card in the Examples gallery and so the
|
||||
// "Use this prompt" flow can prefill `mode=deck`, scenario, and the right
|
||||
// example_prompt.
|
||||
|
||||
import { writeFile, mkdir } from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
||||
const SKILLS = path.join(ROOT, 'skills');
|
||||
const UPSTREAM_URL = 'https://github.com/lewislulu/html-ppt-skill';
|
||||
|
||||
// `featured` is a sort priority used by the Examples gallery — smaller wins
|
||||
// the tie-break, so a curated handful float to the top. Templates without
|
||||
// `featured` slot in alphabetically after the existing skills.
|
||||
const TEMPLATES = [
|
||||
{
|
||||
slug: 'pitch-deck',
|
||||
name: 'html-ppt-pitch-deck',
|
||||
title: 'HTML PPT · Pitch Deck',
|
||||
scenario: 'finance',
|
||||
featured: 20,
|
||||
description:
|
||||
'Investor-ready 10-slide HTML pitch deck — white + blue→purple gradient hero, big numbers, traction bar chart, $4.5M-style ask page. Use when the user wants a fundraising deck, seed-round pitch, or VC meeting slides.',
|
||||
triggers: ['pitch deck', 'pitch', 'fundraising', 'seed round', 'investor deck', 'vc deck', 'pitch slides'],
|
||||
examplePrompt:
|
||||
'Build a 10-slide pitch deck in HTML for my seed round. Use the html-ppt-pitch-deck full-deck template (white + blue→purple gradient, traction bars, $X.XM ask). Confirm three things first: (1) name + one-line pitch, (2) key traction numbers, (3) ask + use of funds.',
|
||||
},
|
||||
{
|
||||
slug: 'product-launch',
|
||||
name: 'html-ppt-product-launch',
|
||||
title: 'HTML PPT · Product Launch',
|
||||
scenario: 'marketing',
|
||||
featured: 21,
|
||||
description:
|
||||
'Launch keynote deck — dark hero + light content, warm orange→peach accent, feature cards, pricing tiers, CTA. Use when announcing a product, launching a feature, or doing a keynote-style reveal.',
|
||||
triggers: ['product launch', 'keynote', 'launch deck', 'feature reveal', 'launch slides', '发布会'],
|
||||
examplePrompt:
|
||||
'Make a product-launch keynote deck in HTML using the html-ppt-product-launch full-deck template (dark hero, warm orange accent, feature cards, pricing tiers). Confirm: product name + tagline, the 3 key features, and pricing tiers — then write the deck.',
|
||||
},
|
||||
{
|
||||
slug: 'tech-sharing',
|
||||
name: 'html-ppt-tech-sharing',
|
||||
title: 'HTML PPT · Tech Sharing',
|
||||
scenario: 'engineering',
|
||||
featured: 22,
|
||||
description:
|
||||
'Conference / internal tech-talk deck — GitHub-dark, JetBrains Mono, terminal code blocks, agenda + Q&A pages. Use for engineering presentations, internal sharing sessions, conference talks, and code-heavy walkthroughs.',
|
||||
triggers: ['tech sharing', 'tech talk', '技术分享', 'engineering talk', 'conference talk', 'dev talk'],
|
||||
examplePrompt:
|
||||
'帮我用 html-ppt-tech-sharing 模板做一份 8 页的技术分享 PPT。先确认:分享主题、目标听众(同事 / 社区 / 客户)、要不要包含代码片段和 benchmark。GitHub 暗色主题 + JetBrains Mono,agenda + Q&A 页备好。',
|
||||
},
|
||||
{
|
||||
slug: 'weekly-report',
|
||||
name: 'html-ppt-weekly-report',
|
||||
title: 'HTML PPT · Weekly Report',
|
||||
scenario: 'operations',
|
||||
featured: 23,
|
||||
description:
|
||||
'Team weekly / status-update deck — corporate clarity, 8-cell KPI grid, shipped list, 8-week bar chart, next-week table. Use for 周报, business reviews, team status updates, and exec dashboards.',
|
||||
triggers: ['weekly report', '周报', 'status update', 'team report', 'business review', 'wbr'],
|
||||
examplePrompt:
|
||||
'用 html-ppt-weekly-report 模板生成一份周报(7 页)。先问我四件事:本周时间范围、3-5 个核心 KPI 数字、本周已发布 / 已完成的事项、下周计划与风险。然后用模板填好 8 周柱状图和下周表格。',
|
||||
},
|
||||
{
|
||||
slug: 'xhs-post',
|
||||
name: 'html-ppt-xhs-post',
|
||||
title: 'HTML PPT · 小红书 图文',
|
||||
scenario: 'marketing',
|
||||
featured: 24,
|
||||
description:
|
||||
'小红书 / Instagram 风 9 页 3:4 竖版图文(810×1080)— 暖色 pastel、虚线 sticker 卡片、底部页码点点。用于发小红书图文、Instagram carousel、品牌种草内容。',
|
||||
triggers: ['小红书', 'xhs', 'xhs post', 'xiaohongshu', '图文', 'instagram carousel', '种草'],
|
||||
examplePrompt:
|
||||
'帮我用 html-ppt-xhs-post 模板做一组 9 张小红书图文(3:4 竖版,810×1080)。先告诉我主题,然后帮我把封面 + 7 页内容 + 结尾 CTA 排好,每页一句标题 + 一段正文 + 关键词 sticker。',
|
||||
},
|
||||
{
|
||||
slug: 'course-module',
|
||||
name: 'html-ppt-course-module',
|
||||
title: 'HTML PPT · Course Module',
|
||||
scenario: 'education',
|
||||
featured: 25,
|
||||
description:
|
||||
'Online-course / workshop module deck — warm paper background + Playfair serif, persistent left sidebar of learning objectives, MCQ self-check page. Use for teaching modules, training materials, workshop slides.',
|
||||
triggers: ['course module', 'course slides', 'workshop', 'training deck', 'lesson', '教学', '课件'],
|
||||
examplePrompt:
|
||||
'Use the html-ppt-course-module template to build a 7-slide module deck. Confirm: module title, 3-5 learning objectives (these stick on the left rail), and the MCQ self-check question. Then assemble the deck with serif headings on warm paper.',
|
||||
},
|
||||
{
|
||||
slug: 'presenter-mode-reveal',
|
||||
name: 'html-ppt-presenter-mode',
|
||||
title: 'HTML PPT · Presenter Mode (演讲者模式)',
|
||||
scenario: 'engineering',
|
||||
featured: 26,
|
||||
description:
|
||||
'演讲者模式专用 deck — tokyo-night 默认主题,5 套主题 T 键切换,每页带 150-300 字逐字稿示例(<aside class="notes">),按 S 打开 popup(CURRENT / NEXT / SCRIPT / TIMER 四张磁吸卡片)。用于技术分享、公开演讲、课程讲解,怕忘词或要提词器的场景。',
|
||||
triggers: ['presenter mode', '演讲者模式', '逐字稿', 'speaker notes', '提词器', 'presenter view', '演讲'],
|
||||
examplePrompt:
|
||||
'用 html-ppt-presenter-mode 模板做一份带逐字稿的演讲 PPT。先确认:演讲主题、时长(每页 2-3 分钟)、目标听众。然后帮我每页写 150-300 字的口语化逐字稿(不是讲稿,是提示信号),按 S 能打开 presenter 弹窗。',
|
||||
},
|
||||
{
|
||||
slug: 'xhs-white-editorial',
|
||||
name: 'html-ppt-xhs-white-editorial',
|
||||
title: 'HTML PPT · 白底杂志风',
|
||||
scenario: 'marketing',
|
||||
featured: 27,
|
||||
description:
|
||||
'白底杂志风 deck — 纯白背景 + 顶部 10 色彩虹 bar、80-110px display 标题、紫→蓝→绿→橙→粉渐变文字、马卡龙软卡片组(粉/紫/蓝/绿/橙)、黑底白字 .focus pill、引用大块。同时适合发小红书图文 + 横版 PPT 双用。',
|
||||
triggers: ['白底杂志', '杂志风', 'xhs editorial', 'white editorial', '小红书白底', 'editorial deck'],
|
||||
examplePrompt:
|
||||
'用 html-ppt-xhs-white-editorial 模板做一份白底杂志风 PPT,中文优先。要点:80-110px display 大标题、彩虹顶部 bar、马卡龙软卡片、黑底白字 .focus pill。先告诉我主题和受众,再写 8-12 页。',
|
||||
},
|
||||
{
|
||||
slug: 'graphify-dark-graph',
|
||||
name: 'html-ppt-graphify-dark-graph',
|
||||
title: 'HTML PPT · 暗底知识图谱',
|
||||
scenario: 'engineering',
|
||||
featured: 28,
|
||||
description:
|
||||
'暗底知识图谱 deck — #06060c→#0e1020 深夜渐变 + 漂浮 blur orbs、封面 SVG 力导向图谱、彩虹渐变标题、JetBrains Mono 命令行高亮、glass-morphism 卡片。适合 dev-tool / CLI / 知识图谱 / 数据可视化的发布会,"AI-native + 科幻 + 暖色" 调子。',
|
||||
triggers: ['知识图谱', 'graph deck', 'dark graph', 'dev tool launch', 'cli launch', 'data viz launch'],
|
||||
examplePrompt:
|
||||
'用 html-ppt-graphify-dark-graph 模板做一份 dev-tool 发布会 PPT。深夜渐变背景 + 力导向图谱封面 + 彩虹标题 + JetBrains Mono 命令行。先确认:工具名、核心能力、demo 步骤;要不要现场敲 CLI。',
|
||||
},
|
||||
{
|
||||
slug: 'knowledge-arch-blueprint',
|
||||
name: 'html-ppt-knowledge-arch-blueprint',
|
||||
title: 'HTML PPT · 奶油蓝图架构',
|
||||
scenario: 'engineering',
|
||||
featured: 29,
|
||||
description:
|
||||
'奶油蓝图架构 deck — 奶油纸 #F0EAE0 底色 + 单一锈红 #B5392A 高亮、48px 蓝图网格 mask、2px 黑边硬卡片、pipeline 步骤盒(其中一个抬高)、右侧锈红 insight callout、Playfair 衬线大字、SVG 虚线反馈环。零渐变零软阴影,认真且印刷友好。',
|
||||
triggers: ['architecture', 'blueprint', 'system design', '架构图', 'data flow', 'engineering whitepaper'],
|
||||
examplePrompt:
|
||||
'用 html-ppt-knowledge-arch-blueprint 模板做一份系统架构介绍 PPT。奶油纸底 + 锈红高亮 + 蓝图网格 + pipeline 抬高一格 + 衬线大字。先告诉我系统名 + 5-7 个核心模块 + 数据流方向,再写 8-10 页。',
|
||||
},
|
||||
{
|
||||
slug: 'hermes-cyber-terminal',
|
||||
name: 'html-ppt-hermes-cyber-terminal',
|
||||
title: 'HTML PPT · 暗终端测评',
|
||||
scenario: 'engineering',
|
||||
featured: 30,
|
||||
description:
|
||||
'暗终端 honest-review deck — #0a0c10 黑底 + 56px 赛博网格 + CRT 暗角 + 扫描线、窗口红绿灯 chrome、`$ prompt` 命令行标题、薄荷绿 #7ed3a4 大字、JetBrains Mono、stroke-only 柱状图、blinking 光标、琥珀/绿/红三档 tag、暗色代码块。适合 CLI / agent / dev tool 测评(含 trace、diff、benchmark)。',
|
||||
triggers: ['terminal review', 'cli review', 'agent review', 'honest review', 'dev tool review', '测评'],
|
||||
examplePrompt:
|
||||
'用 html-ppt-hermes-cyber-terminal 模板做一份 CLI / agent 测评 PPT。深色终端风 + scanlines + 命令行标题 + benchmark 柱状图。先确认:被测评对象、3-5 个对比维度、benchmark 数据。',
|
||||
},
|
||||
{
|
||||
slug: 'obsidian-claude-gradient',
|
||||
name: 'html-ppt-obsidian-claude-gradient',
|
||||
title: 'HTML PPT · GitHub 暗紫渐变',
|
||||
scenario: 'engineering',
|
||||
featured: 31,
|
||||
description:
|
||||
'GitHub 暗紫渐变 deck — GitHub-dark #0d1117 + 紫蓝 radial 环境光 + 60px 网格 mask、居中布局、紫色 pill 标签、三色渐变标题(#a855f7→#60a5fa→#34d399)、GitHub 风代码 palette、紫色左边框高亮块。适合开发者工作流 / MCP / Agent / dev tool 教程,类似 GitHub Blog / Linear Changelog。',
|
||||
triggers: ['github dark', 'developer tutorial', 'mcp tutorial', 'agent tutorial', 'dev workflow', 'changelog deck'],
|
||||
examplePrompt:
|
||||
'用 html-ppt-obsidian-claude-gradient 模板做一份开发者教程 PPT。GitHub 暗紫渐变 + 居中布局 + 紫色 pill + 三色渐变标题 + 配置/步骤代码块。先确认:教什么、目标受众、要不要 MCP/Agent 配置示例。',
|
||||
},
|
||||
{
|
||||
slug: 'testing-safety-alert',
|
||||
name: 'html-ppt-testing-safety-alert',
|
||||
title: 'HTML PPT · 红琥珀警示',
|
||||
scenario: 'engineering',
|
||||
featured: 32,
|
||||
description:
|
||||
'红琥珀警示 deck — 顶/底 45° 红黑 hazard 条纹、红色删除线否定标题、L1/L2/L3 绿/琥珀/红 tier 卡片、圆点状态 alert box、policy-yaml 代码块(红左边框 + bad 关键词高亮)、红绿 checklist、Q1 事故堆叠柱状图。适合安全 / 风险 / 事故复盘 / 红队 / 上线前 AI 评审 / policy-as-code。',
|
||||
triggers: ['safety alert', 'incident', 'red team', 'risk review', '事故复盘', '安全评审', 'policy as code'],
|
||||
examplePrompt:
|
||||
'用 html-ppt-testing-safety-alert 模板做一份事故复盘 / 安全评审 PPT。红黑 hazard 条 + 红色删除线 + L1/L2/L3 tier 卡片 + policy-yaml 代码块。先告诉我事件时间线、根因、影响范围。',
|
||||
},
|
||||
{
|
||||
slug: 'xhs-pastel-card',
|
||||
name: 'html-ppt-xhs-pastel-card',
|
||||
title: 'HTML PPT · 柔和马卡龙慢生活',
|
||||
scenario: 'personal',
|
||||
featured: 33,
|
||||
description:
|
||||
'柔和马卡龙慢生活 deck — 奶油 #fef8f1 底 + 三个柔光 blob、Playfair 斜体衬线 display 标题混 sans 正文、28px 圆角马卡龙卡片(桃 / 薄荷 / 天 / 紫 / 柠 / 玫)、Playfair 斜体 01-04 序号、SVG donut 图、chip+page 顶栏。适合生活方式 / 个人成长 / 慢生活 / 情绪类内容,"杂志、手作、不太科技"的感觉。',
|
||||
triggers: ['pastel', 'macaron', 'lifestyle', 'slow living', '慢生活', '生活方式', '个人成长'],
|
||||
examplePrompt:
|
||||
'用 html-ppt-xhs-pastel-card 模板做一份慢生活主题图文。奶油底 + 马卡龙圆角卡片 + Playfair 斜体序号 + donut 图。先告诉我主题(休息 / 暂停 / 自我照顾…)和 5-7 个想说的点。',
|
||||
},
|
||||
{
|
||||
slug: 'dir-key-nav-minimal',
|
||||
name: 'html-ppt-dir-key-nav-minimal',
|
||||
title: 'HTML PPT · 8 色极简方向键',
|
||||
scenario: 'personal',
|
||||
featured: 34,
|
||||
description:
|
||||
'8 页极简方向键 keynote — 每页一个独立单色背景(靛 / 奶 / 绛 / 翠 / 灰 / 紫 / 白 / 炭),各自配色,160px display 标题 + 4px 短粗 accent 线分隔、箭头 → 前缀的 Mono 列表、左下 ← → kbd 提示 + 右下页码、巨大呼吸留白。适合"有话要说但没什么可看"的 keynote、launch、公开演讲。',
|
||||
triggers: ['minimal keynote', '极简', 'mono color', 'one idea per slide', 'public talk', 'launch keynote'],
|
||||
examplePrompt:
|
||||
'用 html-ppt-dir-key-nav-minimal 模板做一份 8 页极简 keynote。每页一个单色背景 + 一句 160px 大标题 + 几条箭头列表。先告诉我演讲主题,然后帮我把 8 个核心观点拍成 8 页(每页一个 idea)。',
|
||||
},
|
||||
];
|
||||
|
||||
const SKILL_BODY = (t) => `# ${t.title}
|
||||
|
||||
A focused entry point into the [\`html-ppt\`](../html-ppt/SKILL.md) master skill that lands the user directly on the **\`${t.slug}\`** full-deck template.
|
||||
|
||||
## When this card is picked
|
||||
|
||||
The Examples gallery wires "Use this prompt" to the example_prompt above. When you accept that prompt, this card is the right pick if the user wants exactly the visual identity of \`${t.slug}\` (see the upstream [full-decks catalog](../html-ppt/references/full-decks.md) for screenshots and rationale).
|
||||
|
||||
## How to author the deck
|
||||
|
||||
1. **Read the master skill first.** All authoring rules live in
|
||||
[\`skills/html-ppt/SKILL.md\`](../html-ppt/SKILL.md) — content/audience checklist,
|
||||
token rules, layout reuse, presenter mode, the keyboard runtime, and the
|
||||
"never put presenter-only text on the slide" rule.
|
||||
2. **Start from the matching template folder:**
|
||||
\`skills/html-ppt/templates/full-decks/${t.slug}/\` — copy \`index.html\` and
|
||||
\`style.css\` into the project, keep the \`.tpl-${t.slug}\` body class.
|
||||
3. **Bring the shared runtime with the template.** The upstream
|
||||
\`index.html\` links the shared CSS/JS via \`../../../assets/...\` because it
|
||||
sits three folders deep inside \`skills/html-ppt/templates/full-decks/\`.
|
||||
Once you copy \`index.html\` into the project, those parent-relative URLs
|
||||
no longer resolve and \`base.css\`, \`animations.css\`, and \`runtime.js\`
|
||||
will 404 — meaning the deck never activates and slide navigation is
|
||||
dead. Pick one of these two recipes per project:
|
||||
- **Recipe A — copy + rewrite (preferred):** copy
|
||||
\`skills/html-ppt/assets/fonts.css\`, \`skills/html-ppt/assets/base.css\`,
|
||||
\`skills/html-ppt/assets/animations/animations.css\`, and
|
||||
\`skills/html-ppt/assets/runtime.js\` into a project-local
|
||||
\`assets/\` (with \`assets/animations/animations.css\`), then rewrite the
|
||||
four \`<link>\`/\`<script>\` tags in \`index.html\` from
|
||||
\`../../../assets/...\` to the matching project-local paths
|
||||
(\`assets/fonts.css\`, \`assets/base.css\`,
|
||||
\`assets/animations/animations.css\`, \`assets/runtime.js\`).
|
||||
- **Recipe B — inline:** read the same four files and replace each
|
||||
\`<link rel="stylesheet" href="../../../assets/...">\` with a
|
||||
\`<style>...</style>\` containing the file's contents, and the
|
||||
\`<script src="../../../assets/runtime.js">\` with a
|
||||
\`<script>...</script>\` containing \`runtime.js\`. Yields a single
|
||||
self-contained \`index.html\`.
|
||||
Either way, do not ship the upstream \`../../../assets/...\` URLs
|
||||
verbatim into a project artifact — they only work in-tree.
|
||||
4. **Pick a theme.** Default tokens look fine; if the user wants a different
|
||||
feel, swap in any of the 36 themes from \`skills/html-ppt/assets/themes/*.css\`
|
||||
via \`<link id="theme-link">\` and let \`T\` cycle.
|
||||
5. **Replace demo content, not classes.** The \`.tpl-${t.slug}\` scoped CSS only
|
||||
recognises the structural classes shipped in the template — keep them.
|
||||
6. **Speaker notes go inside \`<aside class="notes">\` or \`<div class="notes">\`** — never as visible text on the slide.
|
||||
|
||||
## Attribution
|
||||
|
||||
Visual system, layouts, themes and the runtime keyboard model come from
|
||||
the upstream MIT-licensed [\`lewislulu/html-ppt-skill\`](${UPSTREAM_URL}). The
|
||||
LICENSE file ships at \`skills/html-ppt/LICENSE\`; please keep it in place when
|
||||
redistributing.
|
||||
`;
|
||||
|
||||
function frontmatter(t) {
|
||||
const triggers = t.triggers
|
||||
.map((s) => ` - "${s.replace(/"/g, '\\"')}"`)
|
||||
.join('\n');
|
||||
return [
|
||||
'---',
|
||||
`name: ${t.name}`,
|
||||
`description: ${t.description}`,
|
||||
'triggers:',
|
||||
triggers,
|
||||
'od:',
|
||||
' mode: deck',
|
||||
` scenario: ${t.scenario}`,
|
||||
` featured: ${t.featured}`,
|
||||
` upstream: "${UPSTREAM_URL}"`,
|
||||
' preview:',
|
||||
' type: html',
|
||||
' entry: index.html',
|
||||
' design_system:',
|
||||
' requires: false',
|
||||
' speaker_notes: true',
|
||||
' animations: true',
|
||||
` example_prompt: ${JSON.stringify(t.examplePrompt)}`,
|
||||
'---',
|
||||
'',
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
let wrote = 0;
|
||||
for (const t of TEMPLATES) {
|
||||
const dir = path.join(SKILLS, `html-ppt-${t.slug}`);
|
||||
await mkdir(dir, { recursive: true });
|
||||
const skillMd = frontmatter(t) + SKILL_BODY(t);
|
||||
await writeFile(path.join(dir, 'SKILL.md'), skillMd, 'utf8');
|
||||
wrote++;
|
||||
}
|
||||
console.log(`[scaffold] wrote ${wrote} html-ppt-* SKILL.md files`);
|
||||
79
skills/html-ppt-course-module/SKILL.md
Normal file
79
skills/html-ppt-course-module/SKILL.md
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
---
|
||||
name: html-ppt-course-module
|
||||
description: Online-course / workshop module deck — warm paper background + Playfair serif, persistent left sidebar of learning objectives, MCQ self-check page. Use for teaching modules, training materials, workshop slides.
|
||||
triggers:
|
||||
- "course module"
|
||||
- "course slides"
|
||||
- "workshop"
|
||||
- "training deck"
|
||||
- "lesson"
|
||||
- "教学"
|
||||
- "课件"
|
||||
od:
|
||||
mode: deck
|
||||
scenario: education
|
||||
featured: 25
|
||||
upstream: "https://github.com/lewislulu/html-ppt-skill"
|
||||
preview:
|
||||
type: html
|
||||
entry: index.html
|
||||
design_system:
|
||||
requires: false
|
||||
speaker_notes: true
|
||||
animations: true
|
||||
example_prompt: "Use the html-ppt-course-module template to build a 7-slide module deck. Confirm: module title, 3-5 learning objectives (these stick on the left rail), and the MCQ self-check question. Then assemble the deck with serif headings on warm paper."
|
||||
---
|
||||
# HTML PPT · Course Module
|
||||
|
||||
A focused entry point into the [`html-ppt`](../html-ppt/SKILL.md) master skill that lands the user directly on the **`course-module`** full-deck template.
|
||||
|
||||
## When this card is picked
|
||||
|
||||
The Examples gallery wires "Use this prompt" to the example_prompt above. When you accept that prompt, this card is the right pick if the user wants exactly the visual identity of `course-module` (see the upstream [full-decks catalog](../html-ppt/references/full-decks.md) for screenshots and rationale).
|
||||
|
||||
## How to author the deck
|
||||
|
||||
1. **Read the master skill first.** All authoring rules live in
|
||||
[`skills/html-ppt/SKILL.md`](../html-ppt/SKILL.md) — content/audience checklist,
|
||||
token rules, layout reuse, presenter mode, the keyboard runtime, and the
|
||||
"never put presenter-only text on the slide" rule.
|
||||
2. **Start from the matching template folder:**
|
||||
`skills/html-ppt/templates/full-decks/course-module/` — copy `index.html` and
|
||||
`style.css` into the project, keep the `.tpl-course-module` body class.
|
||||
3. **Bring the shared runtime with the template.** The upstream
|
||||
`index.html` links the shared CSS/JS via `../../../assets/...` because it
|
||||
sits three folders deep inside `skills/html-ppt/templates/full-decks/`.
|
||||
Once you copy `index.html` into the project, those parent-relative URLs
|
||||
no longer resolve and `base.css`, `animations.css`, and `runtime.js`
|
||||
will 404 — meaning the deck never activates and slide navigation is
|
||||
dead. Pick one of these two recipes per project:
|
||||
- **Recipe A — copy + rewrite (preferred):** copy
|
||||
`skills/html-ppt/assets/fonts.css`, `skills/html-ppt/assets/base.css`,
|
||||
`skills/html-ppt/assets/animations/animations.css`, and
|
||||
`skills/html-ppt/assets/runtime.js` into a project-local
|
||||
`assets/` (with `assets/animations/animations.css`), then rewrite the
|
||||
four `<link>`/`<script>` tags in `index.html` from
|
||||
`../../../assets/...` to the matching project-local paths
|
||||
(`assets/fonts.css`, `assets/base.css`,
|
||||
`assets/animations/animations.css`, `assets/runtime.js`).
|
||||
- **Recipe B — inline:** read the same four files and replace each
|
||||
`<link rel="stylesheet" href="../../../assets/...">` with a
|
||||
`<style>...</style>` containing the file's contents, and the
|
||||
`<script src="../../../assets/runtime.js">` with a
|
||||
`<script>...</script>` containing `runtime.js`. Yields a single
|
||||
self-contained `index.html`.
|
||||
Either way, do not ship the upstream `../../../assets/...` URLs
|
||||
verbatim into a project artifact — they only work in-tree.
|
||||
4. **Pick a theme.** Default tokens look fine; if the user wants a different
|
||||
feel, swap in any of the 36 themes from `skills/html-ppt/assets/themes/*.css`
|
||||
via `<link id="theme-link">` and let `T` cycle.
|
||||
5. **Replace demo content, not classes.** The `.tpl-course-module` scoped CSS only
|
||||
recognises the structural classes shipped in the template — keep them.
|
||||
6. **Speaker notes go inside `<aside class="notes">` or `<div class="notes">`** — never as visible text on the slide.
|
||||
|
||||
## Attribution
|
||||
|
||||
Visual system, layouts, themes and the runtime keyboard model come from
|
||||
the upstream MIT-licensed [`lewislulu/html-ppt-skill`](https://github.com/lewislulu/html-ppt-skill). The
|
||||
LICENSE file ships at `skills/html-ppt/LICENSE`; please keep it in place when
|
||||
redistributing.
|
||||
542
skills/html-ppt-course-module/example.html
Normal file
542
skills/html-ppt-course-module/example.html
Normal file
|
|
@ -0,0 +1,542 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Module 04 · Recursion · CS101</title>
|
||||
<style>/* html-ppt :: shared webfonts */
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@200;300;400;500;600;700;800;900&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@200;300;400;500;600;700;900&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@300;400;600;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,600;0,800;1,400&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@300;400;500;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Archivo+Black&display=swap');
|
||||
|
||||
</style>
|
||||
<style>/* html-ppt :: base.css — reset + shared tokens + layout primitives */
|
||||
/* Default tokens. Themes in assets/themes/*.css override the :root block. */
|
||||
:root {
|
||||
--bg: #ffffff;
|
||||
--bg-soft: #f7f7f8;
|
||||
--surface: #ffffff;
|
||||
--surface-2: #f2f2f4;
|
||||
--border: rgba(0,0,0,.08);
|
||||
--border-strong: rgba(0,0,0,.16);
|
||||
--text-1: #111216;
|
||||
--text-2: #55596a;
|
||||
--text-3: #8a8f9e;
|
||||
--accent: #3b6cff;
|
||||
--accent-2: #7a5cff;
|
||||
--accent-3: #ff5c8a;
|
||||
--good: #1aaf6c;
|
||||
--warn: #f5a524;
|
||||
--bad: #e0445a;
|
||||
--grad: linear-gradient(135deg,#3b6cff,#7a5cff 55%,#ff5c8a);
|
||||
--grad-soft: linear-gradient(135deg,#eef2ff,#f5ecff 55%,#ffeef5);
|
||||
--radius: 18px;
|
||||
--radius-sm: 12px;
|
||||
--radius-lg: 26px;
|
||||
--shadow: 0 10px 30px rgba(18,24,40,.08), 0 2px 6px rgba(18,24,40,.04);
|
||||
--shadow-lg: 0 24px 60px rgba(18,24,40,.14), 0 6px 16px rgba(18,24,40,.06);
|
||||
--font-sans: 'Inter','Noto Sans SC',-apple-system,BlinkMacSystemFont,Helvetica,Arial,sans-serif;
|
||||
--font-serif: 'Playfair Display','Noto Serif SC',Georgia,serif;
|
||||
--font-mono: 'JetBrains Mono','IBM Plex Mono',SFMono-Regular,Menlo,monospace;
|
||||
--font-display: var(--font-sans);
|
||||
--letter-tight: -.03em;
|
||||
--letter-normal: -.01em;
|
||||
--ease: cubic-bezier(.4,0,.2,1);
|
||||
}
|
||||
|
||||
*,*::before,*::after{box-sizing:border-box}
|
||||
html,body{margin:0;padding:0;background:var(--bg);color:var(--text-1);
|
||||
font-family:var(--font-sans);font-weight:400;line-height:1.6;
|
||||
-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;
|
||||
letter-spacing:var(--letter-normal)}
|
||||
img,svg,video{max-width:100%;display:block}
|
||||
a{color:var(--accent);text-decoration:none}
|
||||
a:hover{text-decoration:underline}
|
||||
code,kbd,pre,samp{font-family:var(--font-mono)}
|
||||
|
||||
/* ================= SLIDE SYSTEM ================= */
|
||||
.deck{position:relative;width:100vw;height:100vh;overflow:hidden;background:var(--bg)}
|
||||
.slide{
|
||||
position:absolute;inset:0;
|
||||
display:flex;flex-direction:column;justify-content:center;
|
||||
padding:72px 96px;
|
||||
box-sizing:border-box;
|
||||
opacity:0;pointer-events:none;
|
||||
transition:opacity .5s var(--ease), transform .5s var(--ease);
|
||||
transform:translateX(30px);
|
||||
overflow:hidden;
|
||||
}
|
||||
.slide.is-active{opacity:1;pointer-events:auto;transform:translateX(0);z-index:2}
|
||||
.slide.is-prev{transform:translateX(-30px)}
|
||||
|
||||
/* single-page standalone (used when a layout file is opened directly) */
|
||||
body.single .slide{position:relative;width:100vw;height:100vh;opacity:1;transform:none;pointer-events:auto}
|
||||
|
||||
/* ================= TYPOGRAPHY ================= */
|
||||
.eyebrow{font-size:13px;font-weight:500;letter-spacing:.16em;text-transform:uppercase;color:var(--text-3)}
|
||||
.kicker{font-size:14px;font-weight:600;color:var(--accent);letter-spacing:.08em;text-transform:uppercase}
|
||||
h1.title,.h1{font-family:var(--font-display);font-size:72px;line-height:1.05;font-weight:800;letter-spacing:var(--letter-tight);margin:0 0 18px;color:var(--text-1)}
|
||||
h2.title,.h2{font-family:var(--font-display);font-size:54px;line-height:1.1;font-weight:700;letter-spacing:var(--letter-tight);margin:0 0 14px}
|
||||
h3,.h3{font-size:32px;line-height:1.2;font-weight:600;letter-spacing:var(--letter-normal);margin:0 0 10px}
|
||||
h4,.h4{font-size:22px;line-height:1.3;font-weight:600;margin:0 0 8px}
|
||||
.lede{font-size:22px;line-height:1.55;color:var(--text-2);font-weight:300;max-width:62ch}
|
||||
.dim{color:var(--text-2)}
|
||||
.dim2{color:var(--text-3)}
|
||||
.mono{font-family:var(--font-mono)}
|
||||
.serif{font-family:var(--font-serif)}
|
||||
.gradient-text{background:var(--grad);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;color:transparent}
|
||||
|
||||
/* ================= LAYOUT PRIMITIVES ================= */
|
||||
.stack>*+*{margin-top:14px}
|
||||
.row{display:flex;gap:24px;align-items:center}
|
||||
.row.wrap{flex-wrap:wrap}
|
||||
.grid{display:grid;gap:24px}
|
||||
.g2{grid-template-columns:repeat(2,1fr)}
|
||||
.g3{grid-template-columns:repeat(3,1fr)}
|
||||
.g4{grid-template-columns:repeat(4,1fr)}
|
||||
.center{display:flex;align-items:center;justify-content:center;text-align:center}
|
||||
.fill{flex:1}
|
||||
.sp-t{padding-top:24px}.sp-b{padding-bottom:24px}
|
||||
.mt-s{margin-top:8px}.mt-m{margin-top:18px}.mt-l{margin-top:32px}
|
||||
.mb-s{margin-bottom:8px}.mb-m{margin-bottom:18px}.mb-l{margin-bottom:32px}
|
||||
|
||||
/* ================= CARDS ================= */
|
||||
.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);
|
||||
padding:26px 28px;box-shadow:var(--shadow);position:relative;overflow:hidden}
|
||||
.card-soft{background:var(--surface-2);border:1px solid var(--border)}
|
||||
.card-outline{background:transparent;border:1.5px solid var(--border-strong);box-shadow:none}
|
||||
.card-accent{background:var(--surface);border-top:3px solid var(--accent)}
|
||||
.card-hover{transition:transform .3s var(--ease),box-shadow .3s var(--ease)}
|
||||
.card-hover:hover{transform:translateY(-4px);box-shadow:var(--shadow-lg)}
|
||||
|
||||
.pill{display:inline-block;padding:4px 12px;border-radius:999px;font-size:12px;font-weight:500;
|
||||
background:var(--surface-2);color:var(--text-2);border:1px solid var(--border)}
|
||||
.pill-accent{background:color-mix(in srgb,var(--accent) 12%,transparent);color:var(--accent);border-color:color-mix(in srgb,var(--accent) 28%,transparent)}
|
||||
|
||||
/* ================= BARS / DIVIDERS ================= */
|
||||
.divider{height:1px;background:var(--border);width:100%}
|
||||
.divider-accent{height:3px;width:72px;background:var(--accent);border-radius:2px}
|
||||
|
||||
/* ================= CHROME (header/footer/progress) ================= */
|
||||
.deck-header{position:absolute;top:24px;left:40px;right:40px;display:flex;align-items:center;justify-content:space-between;
|
||||
font-size:12px;color:var(--text-3);letter-spacing:.12em;text-transform:uppercase;z-index:10;pointer-events:none}
|
||||
.deck-footer{position:absolute;bottom:24px;left:40px;right:40px;display:flex;align-items:center;justify-content:space-between;
|
||||
font-size:12px;color:var(--text-3);z-index:10;pointer-events:none}
|
||||
.slide-number::before{content:attr(data-current)}
|
||||
.slide-number::after{content:" / " attr(data-total)}
|
||||
.progress-bar{position:fixed;left:0;right:0;bottom:0;height:3px;background:transparent;z-index:20}
|
||||
.progress-bar > span{display:block;height:100%;width:0;background:var(--accent);transition:width .3s var(--ease)}
|
||||
|
||||
/* ================= PRESENTER / OVERVIEW ================= */
|
||||
.notes{display:none!important}
|
||||
.notes-overlay{position:fixed;inset:auto 0 0 0;max-height:42vh;background:rgba(20,22,30,.95);color:#e8ebf4;
|
||||
padding:20px 32px;font-size:16px;line-height:1.6;border-top:1px solid rgba(255,255,255,.1);transform:translateY(100%);
|
||||
transition:transform .3s var(--ease);z-index:40;overflow:auto;font-family:var(--font-sans)}
|
||||
.notes-overlay.open{transform:translateY(0)}
|
||||
.overview{position:fixed;inset:0;background:rgba(10,12,18,.92);backdrop-filter:blur(12px);z-index:50;
|
||||
display:none;padding:40px;overflow:auto}
|
||||
.overview.open{display:grid;grid-template-columns:repeat(4,1fr);gap:20px;align-content:start}
|
||||
.overview .thumb{background:var(--surface);border:1px solid var(--border);border-radius:12px;
|
||||
aspect-ratio:16/9;overflow:hidden;cursor:pointer;position:relative;color:var(--text-1);padding:16px;
|
||||
font-size:11px;transition:transform .2s var(--ease)}
|
||||
.overview .thumb:hover{transform:scale(1.04)}
|
||||
.overview .thumb .n{position:absolute;top:8px;left:10px;font-weight:700;font-size:14px;color:var(--text-3)}
|
||||
.overview .thumb .t{position:absolute;bottom:10px;left:14px;right:14px;font-weight:600;color:var(--text-1)}
|
||||
|
||||
/* ================= PRESENTER VIEW ================= */
|
||||
/* Presenter view opens in a separate popup window (S key).
|
||||
* All presenter styles are self-contained in the popup HTML generated by runtime.js.
|
||||
* The audience window (this file) is NOT affected — it stays as normal deck view.
|
||||
* Only the .notes class below is needed to hide speaker notes from audience. */
|
||||
|
||||
/* ================= UTILITY ================= */
|
||||
.hidden{display:none!important}
|
||||
.nowrap{white-space:nowrap}
|
||||
.tr{text-align:right}.tc{text-align:center}.tl{text-align:left}
|
||||
.uppercase{text-transform:uppercase;letter-spacing:.12em}
|
||||
|
||||
/* ================= PRINT ================= */
|
||||
@media print{
|
||||
.slide{position:relative;opacity:1!important;transform:none!important;page-break-after:always;height:100vh}
|
||||
.deck-header,.deck-footer,.progress-bar,.notes-overlay,.overview{display:none!important}
|
||||
}
|
||||
|
||||
</style>
|
||||
<style>/* html-ppt :: animations.css
|
||||
* Apply by adding class="anim-<name>" or data-anim="<name>".
|
||||
* Durations are deliberately snappy; tweak --anim-dur per element.
|
||||
*/
|
||||
:root{--anim-dur:.7s;--anim-ease:cubic-bezier(.4,0,.2,1)}
|
||||
|
||||
/* ---------- FADE DIRECTIONALS ---------- */
|
||||
@keyframes kf-fade-up{from{opacity:0;transform:translateY(32px)}to{opacity:1;transform:none}}
|
||||
@keyframes kf-fade-down{from{opacity:0;transform:translateY(-32px)}to{opacity:1;transform:none}}
|
||||
@keyframes kf-fade-left{from{opacity:0;transform:translateX(-40px)}to{opacity:1;transform:none}}
|
||||
@keyframes kf-fade-right{from{opacity:0;transform:translateX(40px)}to{opacity:1;transform:none}}
|
||||
.anim-fade-up{animation:kf-fade-up var(--anim-dur) var(--anim-ease) both}
|
||||
.anim-fade-down{animation:kf-fade-down var(--anim-dur) var(--anim-ease) both}
|
||||
.anim-fade-left{animation:kf-fade-left var(--anim-dur) var(--anim-ease) both}
|
||||
.anim-fade-right{animation:kf-fade-right var(--anim-dur) var(--anim-ease) both}
|
||||
|
||||
/* ---------- RISE / DROP / ZOOM / BLUR / GLITCH ---------- */
|
||||
@keyframes kf-rise{from{opacity:0;transform:translateY(60px) scale(.97);filter:blur(6px)}to{opacity:1;transform:none;filter:none}}
|
||||
@keyframes kf-drop{from{opacity:0;transform:translateY(-60px) scale(.97)}to{opacity:1;transform:none}}
|
||||
@keyframes kf-zoom{0%{opacity:0;transform:scale(.6)}60%{transform:scale(1.04)}100%{opacity:1;transform:scale(1)}}
|
||||
@keyframes kf-blur{from{opacity:0;filter:blur(18px)}to{opacity:1;filter:none}}
|
||||
@keyframes kf-glitch{0%{opacity:0;transform:translateX(0);clip-path:inset(0 0 0 0)}
|
||||
20%{opacity:1;transform:translateX(-6px);clip-path:inset(20% 0 30% 0)}
|
||||
40%{transform:translateX(4px);clip-path:inset(50% 0 10% 0)}
|
||||
60%{transform:translateX(-3px);clip-path:inset(10% 0 60% 0)}
|
||||
80%{transform:translateX(2px);clip-path:inset(0 0 0 0)}
|
||||
100%{opacity:1;transform:none}}
|
||||
.anim-rise-in{animation:kf-rise .9s var(--anim-ease) both}
|
||||
.anim-drop-in{animation:kf-drop .8s var(--anim-ease) both}
|
||||
.anim-zoom-pop{animation:kf-zoom .7s cubic-bezier(.22,1.3,.36,1) both}
|
||||
.anim-blur-in{animation:kf-blur .8s var(--anim-ease) both}
|
||||
.anim-glitch-in{animation:kf-glitch .8s steps(5,end) both}
|
||||
|
||||
/* ---------- TYPEWRITER ---------- */
|
||||
.anim-typewriter{display:inline-block;overflow:hidden;white-space:nowrap;border-right:2px solid currentColor;
|
||||
width:0;animation:kf-type 2.4s steps(40,end) forwards, kf-caret 1s step-end infinite}
|
||||
@keyframes kf-type{to{width:100%}}
|
||||
@keyframes kf-caret{50%{border-color:transparent}}
|
||||
|
||||
/* ---------- GLOW / SHIMMER / GRADIENT-FLOW ---------- */
|
||||
@keyframes kf-neon{0%,100%{text-shadow:0 0 8px var(--accent),0 0 20px var(--accent)}
|
||||
50%{text-shadow:0 0 16px var(--accent),0 0 40px var(--accent),0 0 80px var(--accent)}}
|
||||
.anim-neon-glow{animation:kf-neon 2s ease-in-out infinite}
|
||||
|
||||
.anim-shimmer-sweep{position:relative;overflow:hidden}
|
||||
.anim-shimmer-sweep::after{content:"";position:absolute;inset:0;
|
||||
background:linear-gradient(110deg,transparent 40%,rgba(255,255,255,.55) 50%,transparent 60%);
|
||||
transform:translateX(-100%);animation:kf-shimmer 2.4s var(--anim-ease) infinite}
|
||||
@keyframes kf-shimmer{to{transform:translateX(100%)}}
|
||||
|
||||
.anim-gradient-flow{background:linear-gradient(90deg,var(--accent),var(--accent-2,var(--accent)),var(--accent-3,var(--accent)),var(--accent));
|
||||
background-size:300% 100%;-webkit-background-clip:text;background-clip:text;color:transparent;-webkit-text-fill-color:transparent;
|
||||
animation:kf-gradflow 4s linear infinite}
|
||||
@keyframes kf-gradflow{to{background-position:300% 0}}
|
||||
|
||||
/* ---------- STAGGER LIST ---------- */
|
||||
.anim-stagger-list > *{opacity:0;animation:kf-rise .65s var(--anim-ease) both}
|
||||
.anim-stagger-list > *:nth-child(1){animation-delay:.05s}
|
||||
.anim-stagger-list > *:nth-child(2){animation-delay:.15s}
|
||||
.anim-stagger-list > *:nth-child(3){animation-delay:.25s}
|
||||
.anim-stagger-list > *:nth-child(4){animation-delay:.35s}
|
||||
.anim-stagger-list > *:nth-child(5){animation-delay:.45s}
|
||||
.anim-stagger-list > *:nth-child(6){animation-delay:.55s}
|
||||
.anim-stagger-list > *:nth-child(7){animation-delay:.65s}
|
||||
.anim-stagger-list > *:nth-child(8){animation-delay:.75s}
|
||||
.anim-stagger-list > *:nth-child(n+9){animation-delay:.85s}
|
||||
|
||||
/* ---------- COUNTER-UP (JS-driven, marker class only) ---------- */
|
||||
.counter{font-variant-numeric:tabular-nums}
|
||||
|
||||
/* ---------- SVG PATH DRAW ---------- */
|
||||
.anim-path-draw path,.anim-path-draw line,.anim-path-draw polyline,.anim-path-draw circle,.anim-path-draw rect{
|
||||
stroke-dasharray:1000;stroke-dashoffset:1000;animation:kf-draw 2s var(--anim-ease) forwards}
|
||||
@keyframes kf-draw{to{stroke-dashoffset:0}}
|
||||
|
||||
/* ---------- PARALLAX TILT (hover) ---------- */
|
||||
.anim-parallax-tilt{transform-style:preserve-3d;transition:transform .4s var(--anim-ease)}
|
||||
.anim-parallax-tilt:hover{transform:perspective(900px) rotateX(6deg) rotateY(-8deg) translateZ(10px)}
|
||||
|
||||
/* ---------- CARD FLIP 3D ---------- */
|
||||
@keyframes kf-flip{from{transform:perspective(1200px) rotateY(-90deg);opacity:0}
|
||||
to{transform:perspective(1200px) rotateY(0);opacity:1}}
|
||||
.anim-card-flip-3d{animation:kf-flip .9s var(--anim-ease) both;transform-style:preserve-3d;backface-visibility:hidden}
|
||||
|
||||
/* ---------- CUBE ROTATE 3D ---------- */
|
||||
@keyframes kf-cube{from{transform:perspective(1200px) rotateX(20deg) rotateY(-90deg) translateZ(-200px);opacity:0}
|
||||
to{transform:perspective(1200px) rotateX(0) rotateY(0) translateZ(0);opacity:1}}
|
||||
.anim-cube-rotate-3d{animation:kf-cube 1s var(--anim-ease) both}
|
||||
|
||||
/* ---------- PAGE TURN 3D ---------- */
|
||||
@keyframes kf-pageturn{from{transform:perspective(1600px) rotateY(-85deg);transform-origin:left center;opacity:0}
|
||||
to{transform:perspective(1600px) rotateY(0);opacity:1}}
|
||||
.anim-page-turn-3d{animation:kf-pageturn 1s var(--anim-ease) both;transform-origin:left center}
|
||||
|
||||
/* ---------- PERSPECTIVE ZOOM ---------- */
|
||||
@keyframes kf-pzoom{from{opacity:0;transform:perspective(1400px) translateZ(-400px) rotateX(12deg)}
|
||||
to{opacity:1;transform:none}}
|
||||
.anim-perspective-zoom{animation:kf-pzoom 1s var(--anim-ease) both}
|
||||
|
||||
/* ---------- MARQUEE SCROLL ---------- */
|
||||
.anim-marquee-scroll{display:flex;gap:48px;white-space:nowrap;animation:kf-marquee 20s linear infinite}
|
||||
@keyframes kf-marquee{from{transform:translateX(0)}to{transform:translateX(-50%)}}
|
||||
|
||||
/* ---------- KEN BURNS ---------- */
|
||||
@keyframes kf-kenburns{0%{transform:scale(1) translate(0,0)}100%{transform:scale(1.15) translate(-2%,-1%)}}
|
||||
.anim-kenburns{animation:kf-kenburns 14s ease-in-out infinite alternate}
|
||||
|
||||
/* ---------- CONFETTI BURST (pseudo — pure CSS sparkles) ---------- */
|
||||
.anim-confetti-burst{position:relative}
|
||||
.anim-confetti-burst::before,.anim-confetti-burst::after{
|
||||
content:"";position:absolute;top:50%;left:50%;width:8px;height:8px;border-radius:50%;
|
||||
background:var(--accent);box-shadow:
|
||||
20px -30px 0 var(--accent-2,var(--accent)),-25px -20px 0 var(--accent-3,var(--accent)),
|
||||
30px 20px 0 var(--good,#1aaf6c),-30px 25px 0 var(--warn,#f5a524),
|
||||
40px -10px 0 var(--bad,#e0445a),-45px 0 0 var(--accent),
|
||||
10px 40px 0 var(--accent-2,var(--accent)),-15px -40px 0 var(--accent-3,var(--accent));
|
||||
opacity:0;animation:kf-confetti 1.2s var(--anim-ease) forwards}
|
||||
.anim-confetti-burst::after{animation-delay:.15s;transform:rotate(45deg)}
|
||||
@keyframes kf-confetti{0%{opacity:0;transform:scale(.2)}30%{opacity:1}100%{opacity:0;transform:scale(2.2)}}
|
||||
|
||||
/* ---------- SPOTLIGHT ---------- */
|
||||
@keyframes kf-spot{0%{clip-path:circle(0% at 50% 50%)}100%{clip-path:circle(140% at 50% 50%)}}
|
||||
.anim-spotlight{animation:kf-spot 1.1s var(--anim-ease) both}
|
||||
|
||||
/* ---------- MORPH SHAPE (SVG) ---------- */
|
||||
.anim-morph-shape path{animation:kf-morph 6s ease-in-out infinite alternate}
|
||||
@keyframes kf-morph{0%{d:path("M60,120 Q120,20 180,120 T300,120")}
|
||||
100%{d:path("M60,120 Q120,220 180,120 T300,120")}}
|
||||
|
||||
/* ---------- RIPPLE REVEAL ---------- */
|
||||
@keyframes kf-ripple{0%{clip-path:circle(0% at 20% 80%);opacity:.4}
|
||||
100%{clip-path:circle(160% at 20% 80%);opacity:1}}
|
||||
.anim-ripple-reveal{animation:kf-ripple 1.2s var(--anim-ease) both}
|
||||
|
||||
/* reduced motion */
|
||||
@media (prefers-reduced-motion: reduce){
|
||||
[class*="anim-"]{animation:none!important;transition:none!important}
|
||||
}
|
||||
|
||||
</style>
|
||||
<style>/* course-module — academic but friendly */
|
||||
.tpl-course-module{
|
||||
--bg:#fbfaf6;--bg-soft:#f4f1e8;--surface:#ffffff;--surface-2:#f6f3ea;
|
||||
--border:rgba(60,45,20,.12);--border-strong:rgba(60,45,20,.24);
|
||||
--text-1:#2a2418;--text-2:#5a5140;--text-3:#8a7f68;
|
||||
--accent:#2d7d6e;--accent-2:#d88a3a;--accent-3:#c4593f;
|
||||
--grad:linear-gradient(135deg,#2d7d6e,#4ea893);
|
||||
--radius:14px;--radius-lg:20px;
|
||||
--shadow:0 12px 30px rgba(60,45,20,.07);
|
||||
font-family:'Inter','Noto Sans SC',sans-serif;
|
||||
}
|
||||
.tpl-course-module .slide{padding:64px 80px;background:var(--bg);display:grid;grid-template-columns:260px 1fr;gap:56px;align-content:start}
|
||||
.tpl-course-module .slide.full{grid-template-columns:1fr;display:flex;flex-direction:column;justify-content:center}
|
||||
.tpl-course-module .sidebar{border-right:1px solid var(--border);padding-right:32px;position:relative}
|
||||
.tpl-course-module .sidebar .brand{font-family:'Playfair Display',serif;font-size:22px;font-weight:700;color:var(--accent)}
|
||||
.tpl-course-module .sidebar .brand::before{content:"✦ ";color:var(--accent-2)}
|
||||
.tpl-course-module .sidebar h5{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.12em;color:var(--text-3);margin:32px 0 12px}
|
||||
.tpl-course-module .obj-list{list-style:none;padding:0;margin:0;font-size:13px;color:var(--text-2);line-height:1.5}
|
||||
.tpl-course-module .obj-list li{padding:8px 0 8px 22px;position:relative;border-bottom:1px dashed var(--border)}
|
||||
.tpl-course-module .obj-list li::before{content:"○";position:absolute;left:0;top:8px;color:var(--accent)}
|
||||
.tpl-course-module .obj-list li.done::before{content:"●";color:var(--accent)}
|
||||
.tpl-course-module .obj-list li.current{color:var(--text-1);font-weight:700}
|
||||
.tpl-course-module .obj-list li.current::before{content:"▸";color:var(--accent-2)}
|
||||
.tpl-course-module .main{min-width:0}
|
||||
.tpl-course-module .h1{font-family:'Playfair Display',serif;font-size:72px;line-height:1.02;font-weight:800;letter-spacing:-.02em;color:var(--text-1)}
|
||||
.tpl-course-module .h2{font-family:'Playfair Display',serif;font-size:48px;line-height:1.1;font-weight:700;letter-spacing:-.015em;color:var(--text-1)}
|
||||
.tpl-course-module h3,.tpl-course-module h4{color:var(--text-1)}
|
||||
.tpl-course-module .kicker{color:var(--accent-2);font-size:12px;font-weight:700;letter-spacing:.14em}
|
||||
.tpl-course-module .lede{font-size:20px;color:var(--text-2);line-height:1.7}
|
||||
.tpl-course-module .callout{border-left:4px solid var(--accent-2);background:var(--surface-2);padding:20px 24px;border-radius:0 var(--radius) var(--radius) 0;margin-top:24px}
|
||||
.tpl-course-module .callout b{color:var(--accent-2)}
|
||||
.tpl-course-module .concept-box{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:24px 26px;box-shadow:var(--shadow)}
|
||||
.tpl-course-module .concept-box h4{margin-top:0;color:var(--accent)}
|
||||
.tpl-course-module .exercise{background:#fff8ed;border:1.5px dashed var(--accent-2);border-radius:var(--radius);padding:24px 28px}
|
||||
.tpl-course-module .exercise::before{content:"✎ Exercise";display:block;font-size:12px;font-weight:700;letter-spacing:.12em;color:var(--accent-2);margin-bottom:10px;text-transform:uppercase}
|
||||
.tpl-course-module .code{background:#2a2418;color:#f4f1e8;border-radius:var(--radius);padding:20px 24px;font-family:'JetBrains Mono',monospace;font-size:14px;line-height:1.7;overflow:auto}
|
||||
.tpl-course-module .code .cmt{color:#8a7f68;font-style:italic}
|
||||
.tpl-course-module .code .kw{color:#e8a770}
|
||||
.tpl-course-module .code .str{color:#8ec6b2}
|
||||
.tpl-course-module .mcq{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:18px 22px;margin-bottom:10px;display:flex;gap:14px;align-items:flex-start;cursor:pointer}
|
||||
.tpl-course-module .mcq .letter{flex:none;width:28px;height:28px;border-radius:50%;border:2px solid var(--text-3);display:flex;align-items:center;justify-content:center;font-weight:700;font-size:13px;color:var(--text-2)}
|
||||
.tpl-course-module .mcq.correct{border-color:var(--accent);background:rgba(45,125,110,.06)}
|
||||
.tpl-course-module .mcq.correct .letter{border-color:var(--accent);background:var(--accent);color:#fff}
|
||||
.tpl-course-module .pill-academic{display:inline-block;padding:4px 12px;border-radius:4px;background:var(--surface-2);border:1px solid var(--border);font-size:12px;color:var(--text-2);font-family:'JetBrains Mono',monospace}
|
||||
.tpl-course-module .slide.full .h1{font-size:88px}
|
||||
.tpl-course-module .deck-footer{color:var(--text-3)}
|
||||
|
||||
</style>
|
||||
<style>
|
||||
/* Static-preview fallback (runtime.js is absent — keep every slide visible) */
|
||||
.deck{height:auto;min-height:100vh;overflow:visible}
|
||||
.slide{position:relative;inset:auto;opacity:1;pointer-events:auto;transform:none;height:100vh;page-break-after:always}
|
||||
.deck-header,.deck-footer,.slide-number,.progress-bar,.notes-overlay,.overview{pointer-events:none}
|
||||
.notes{display:none!important}
|
||||
</style></head>
|
||||
<body class="tpl-course-module">
|
||||
<div class="deck">
|
||||
|
||||
<!-- 1. Cover -->
|
||||
<section class="slide full" data-title="Cover">
|
||||
<p class="kicker">CS 101 · MODULE 04</p>
|
||||
<h1 class="h1 mt-s">Recursion: solving<br>problems by <em>calling yourself</em>.</h1>
|
||||
<p class="lede mt-l" style="max-width:62ch">In this module you'll learn why a function that calls itself is not a trick, but the most natural way to describe problems that contain smaller copies of themselves.</p>
|
||||
<div class="row mt-l" style="gap:16px">
|
||||
<span class="pill-academic">~ 45 min read</span>
|
||||
<span class="pill-academic">prereq · functions, if/else</span>
|
||||
<span class="pill-academic">lang · Python</span>
|
||||
</div>
|
||||
<div class="deck-footer"><span>Dr. A. Rivera · Spring 2026</span><span class="slide-number" data-current="1" data-total="7"></span></div>
|
||||
</section>
|
||||
|
||||
<!-- 2. Objectives -->
|
||||
<section class="slide" data-title="Objectives">
|
||||
<aside class="sidebar">
|
||||
<div class="brand">CS 101 · M04</div>
|
||||
<h5>Learning objectives</h5>
|
||||
<ul class="obj-list">
|
||||
<li class="current">Define recursion</li>
|
||||
<li>Identify a base case</li>
|
||||
<li>Trace a recursive call</li>
|
||||
<li>Convert loop ↔ recursion</li>
|
||||
<li>Recognize when recursion helps</li>
|
||||
</ul>
|
||||
<h5>Module progress</h5>
|
||||
<p class="dim" style="font-size:13px">Page 2 of 7 · ~5 min in</p>
|
||||
</aside>
|
||||
<div class="main">
|
||||
<p class="kicker">OBJECTIVES</p>
|
||||
<h2 class="h2 mt-s">By the end, you will be able to…</h2>
|
||||
<div class="stack mt-l">
|
||||
<div class="concept-box"><h4>① Explain recursion in one sentence.</h4><p class="dim">"A function that solves a problem by calling itself on a smaller version of that problem."</p></div>
|
||||
<div class="concept-box"><h4>② Write a base case that always terminates.</h4><p class="dim">Every recursive function must have an exit door, or it runs forever.</p></div>
|
||||
<div class="concept-box"><h4>③ Trace a call stack on paper.</h4><p class="dim">Given <code>fact(4)</code>, draw the stack frames top-to-bottom.</p></div>
|
||||
<div class="concept-box"><h4>④ Convert a while-loop to a recursive equivalent.</h4><p class="dim">And explain when one is clearer than the other.</p></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 3. Concept -->
|
||||
<section class="slide" data-title="Concept">
|
||||
<aside class="sidebar">
|
||||
<div class="brand">CS 101 · M04</div>
|
||||
<h5>Learning objectives</h5>
|
||||
<ul class="obj-list">
|
||||
<li class="done">Define recursion</li>
|
||||
<li class="current">Identify a base case</li>
|
||||
<li>Trace a recursive call</li>
|
||||
<li>Convert loop ↔ recursion</li>
|
||||
<li>Recognize when recursion helps</li>
|
||||
</ul>
|
||||
<h5>Key terms</h5>
|
||||
<p class="dim" style="font-size:13px">base case · recursive case · call stack · tail call</p>
|
||||
</aside>
|
||||
<div class="main">
|
||||
<p class="kicker">CORE CONCEPT</p>
|
||||
<h2 class="h2 mt-s">Two parts, always.</h2>
|
||||
<p class="lede mt-m">A recursive function has exactly two things inside it: a <b>base case</b> (when to stop) and a <b>recursive case</b> (how to shrink the problem before calling yourself).</p>
|
||||
<div class="callout">
|
||||
<b>Rule of thumb.</b> If you can't name the base case out loud, don't write the recursion yet. Draw it on paper first.
|
||||
</div>
|
||||
<div class="grid g2 mt-l">
|
||||
<div class="concept-box"><h4>Base case</h4><p class="dim">The smallest possible input — one the function answers directly, without calling itself.</p><p class="pill-academic">e.g. <b>n == 0</b></p></div>
|
||||
<div class="concept-box"><h4>Recursive case</h4><p class="dim">Every other input — delegate to a smaller version of the same problem.</p><p class="pill-academic">e.g. <b>n × fact(n-1)</b></p></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 4. Example -->
|
||||
<section class="slide" data-title="Example">
|
||||
<aside class="sidebar">
|
||||
<div class="brand">CS 101 · M04</div>
|
||||
<h5>Learning objectives</h5>
|
||||
<ul class="obj-list">
|
||||
<li class="done">Define recursion</li>
|
||||
<li class="done">Identify a base case</li>
|
||||
<li class="current">Trace a recursive call</li>
|
||||
<li>Convert loop ↔ recursion</li>
|
||||
<li>Recognize when recursion helps</li>
|
||||
</ul>
|
||||
<h5>Try it yourself</h5>
|
||||
<p class="dim" style="font-size:13px">Open repl.it and run the code on the right. Then try <code>fact(10)</code>.</p>
|
||||
</aside>
|
||||
<div class="main">
|
||||
<p class="kicker">WORKED EXAMPLE</p>
|
||||
<h2 class="h2 mt-s">Factorial, 7 lines.</h2>
|
||||
<div class="code mt-m"><pre style="margin:0"><span class="cmt"># fact(n) = n × (n-1) × … × 1, and fact(0) = 1</span>
|
||||
<span class="kw">def</span> fact(n):
|
||||
<span class="kw">if</span> n == <span class="str">0</span>: <span class="cmt"># base case</span>
|
||||
<span class="kw">return</span> <span class="str">1</span>
|
||||
<span class="kw">return</span> n * fact(n - <span class="str">1</span>) <span class="cmt"># recursive case</span>
|
||||
|
||||
<span class="kw">print</span>(fact(<span class="str">4</span>)) <span class="cmt"># → 24</span></pre></div>
|
||||
<div class="callout">
|
||||
<b>Trace fact(4).</b> 4 × fact(3) → 4 × (3 × fact(2)) → 4 × 3 × (2 × fact(1)) → 4 × 3 × 2 × 1 × fact(0) → 4 × 3 × 2 × 1 × 1 = <b>24</b>.
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 5. Exercise -->
|
||||
<section class="slide" data-title="Exercise">
|
||||
<aside class="sidebar">
|
||||
<div class="brand">CS 101 · M04</div>
|
||||
<h5>Learning objectives</h5>
|
||||
<ul class="obj-list">
|
||||
<li class="done">Define recursion</li>
|
||||
<li class="done">Identify a base case</li>
|
||||
<li class="done">Trace a recursive call</li>
|
||||
<li class="current">Convert loop ↔ recursion</li>
|
||||
<li>Recognize when recursion helps</li>
|
||||
</ul>
|
||||
<h5>Time</h5>
|
||||
<p class="dim" style="font-size:13px">~10 minutes · solo</p>
|
||||
</aside>
|
||||
<div class="main">
|
||||
<p class="kicker">EXERCISE 4.1</p>
|
||||
<h2 class="h2 mt-s">Write <em>sum_to(n)</em>.</h2>
|
||||
<p class="lede mt-m">Return <code>1 + 2 + … + n</code> using recursion — no loops allowed.</p>
|
||||
<div class="exercise mt-l">
|
||||
<p style="margin:0;font-size:18px;color:var(--text-1)"><b>Your task</b></p>
|
||||
<ol style="color:var(--text-2);line-height:1.8;margin:10px 0 0">
|
||||
<li>Write the base case. What does <code>sum_to(0)</code> return?</li>
|
||||
<li>Write the recursive case in terms of <code>sum_to(n - 1)</code>.</li>
|
||||
<li>Test it: <code>sum_to(5) == 15</code>, <code>sum_to(10) == 55</code>.</li>
|
||||
<li>Bonus: what happens if you call <code>sum_to(-3)</code>? Fix it.</li>
|
||||
</ol>
|
||||
</div>
|
||||
<p class="dim mt-m" style="font-size:14px">Stuck? Remember: a base case is the smallest input you already know the answer to.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 6. Check understanding -->
|
||||
<section class="slide" data-title="Check">
|
||||
<aside class="sidebar">
|
||||
<div class="brand">CS 101 · M04</div>
|
||||
<h5>Learning objectives</h5>
|
||||
<ul class="obj-list">
|
||||
<li class="done">Define recursion</li>
|
||||
<li class="done">Identify a base case</li>
|
||||
<li class="done">Trace a recursive call</li>
|
||||
<li class="done">Convert loop ↔ recursion</li>
|
||||
<li class="current">Recognize when recursion helps</li>
|
||||
</ul>
|
||||
<h5>Self-assess</h5>
|
||||
<p class="dim" style="font-size:13px">You should get 3/3.</p>
|
||||
</aside>
|
||||
<div class="main">
|
||||
<p class="kicker">CHECK YOUR UNDERSTANDING</p>
|
||||
<h2 class="h2 mt-s">Which function will recurse forever?</h2>
|
||||
<div class="mt-l">
|
||||
<div class="mcq"><div class="letter">A</div><div><b>def f(n): return 1 if n == 0 else n * f(n - 1)</b><p class="dim" style="font-size:13px;margin:4px 0 0">Base case <code>n == 0</code>, shrinks toward it. Terminates.</p></div></div>
|
||||
<div class="mcq correct"><div class="letter">B</div><div><b>def f(n): return n + f(n + 1)</b><p class="dim" style="font-size:13px;margin:4px 0 0"><b style="color:var(--accent)">✓ Correct.</b> No base case, and <code>n</code> grows — infinite recursion.</p></div></div>
|
||||
<div class="mcq"><div class="letter">C</div><div><b>def f(n): return n if n < 2 else f(n - 1) + f(n - 2)</b><p class="dim" style="font-size:13px;margin:4px 0 0">Classic Fibonacci. Base case on <code>n < 2</code>. Terminates.</p></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 7. Summary -->
|
||||
<section class="slide full" data-title="Summary">
|
||||
<p class="kicker">SUMMARY · MODULE 04</p>
|
||||
<h1 class="h1 mt-s">You can now…</h1>
|
||||
<div class="grid g2 mt-l">
|
||||
<div class="concept-box"><h4>✓ Define recursion</h4><p class="dim">A function that calls itself on a smaller input.</p></div>
|
||||
<div class="concept-box"><h4>✓ Write a safe base case</h4><p class="dim">Every recursion needs an exit door.</p></div>
|
||||
<div class="concept-box"><h4>✓ Trace a call stack</h4><p class="dim">You can unwind <code>fact(4)</code> by hand.</p></div>
|
||||
<div class="concept-box"><h4>✓ Judge when to use it</h4><p class="dim">Trees and self-similar problems → recursion. Flat iteration → loop.</p></div>
|
||||
</div>
|
||||
<div class="callout mt-l">
|
||||
<b>Up next · Module 05.</b> Divide & conquer: merge sort. We'll use everything you just learned — but on lists, not numbers.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
</body></html>
|
||||
78
skills/html-ppt-dir-key-nav-minimal/SKILL.md
Normal file
78
skills/html-ppt-dir-key-nav-minimal/SKILL.md
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
---
|
||||
name: html-ppt-dir-key-nav-minimal
|
||||
description: 8 页极简方向键 keynote — 每页一个独立单色背景(靛 / 奶 / 绛 / 翠 / 灰 / 紫 / 白 / 炭),各自配色,160px display 标题 + 4px 短粗 accent 线分隔、箭头 → 前缀的 Mono 列表、左下 ← → kbd 提示 + 右下页码、巨大呼吸留白。适合"有话要说但没什么可看"的 keynote、launch、公开演讲。
|
||||
triggers:
|
||||
- "minimal keynote"
|
||||
- "极简"
|
||||
- "mono color"
|
||||
- "one idea per slide"
|
||||
- "public talk"
|
||||
- "launch keynote"
|
||||
od:
|
||||
mode: deck
|
||||
scenario: personal
|
||||
featured: 34
|
||||
upstream: "https://github.com/lewislulu/html-ppt-skill"
|
||||
preview:
|
||||
type: html
|
||||
entry: index.html
|
||||
design_system:
|
||||
requires: false
|
||||
speaker_notes: true
|
||||
animations: true
|
||||
example_prompt: "用 html-ppt-dir-key-nav-minimal 模板做一份 8 页极简 keynote。每页一个单色背景 + 一句 160px 大标题 + 几条箭头列表。先告诉我演讲主题,然后帮我把 8 个核心观点拍成 8 页(每页一个 idea)。"
|
||||
---
|
||||
# HTML PPT · 8 色极简方向键
|
||||
|
||||
A focused entry point into the [`html-ppt`](../html-ppt/SKILL.md) master skill that lands the user directly on the **`dir-key-nav-minimal`** full-deck template.
|
||||
|
||||
## When this card is picked
|
||||
|
||||
The Examples gallery wires "Use this prompt" to the example_prompt above. When you accept that prompt, this card is the right pick if the user wants exactly the visual identity of `dir-key-nav-minimal` (see the upstream [full-decks catalog](../html-ppt/references/full-decks.md) for screenshots and rationale).
|
||||
|
||||
## How to author the deck
|
||||
|
||||
1. **Read the master skill first.** All authoring rules live in
|
||||
[`skills/html-ppt/SKILL.md`](../html-ppt/SKILL.md) — content/audience checklist,
|
||||
token rules, layout reuse, presenter mode, the keyboard runtime, and the
|
||||
"never put presenter-only text on the slide" rule.
|
||||
2. **Start from the matching template folder:**
|
||||
`skills/html-ppt/templates/full-decks/dir-key-nav-minimal/` — copy `index.html` and
|
||||
`style.css` into the project, keep the `.tpl-dir-key-nav-minimal` body class.
|
||||
3. **Bring the shared runtime with the template.** The upstream
|
||||
`index.html` links the shared CSS/JS via `../../../assets/...` because it
|
||||
sits three folders deep inside `skills/html-ppt/templates/full-decks/`.
|
||||
Once you copy `index.html` into the project, those parent-relative URLs
|
||||
no longer resolve and `base.css`, `animations.css`, and `runtime.js`
|
||||
will 404 — meaning the deck never activates and slide navigation is
|
||||
dead. Pick one of these two recipes per project:
|
||||
- **Recipe A — copy + rewrite (preferred):** copy
|
||||
`skills/html-ppt/assets/fonts.css`, `skills/html-ppt/assets/base.css`,
|
||||
`skills/html-ppt/assets/animations/animations.css`, and
|
||||
`skills/html-ppt/assets/runtime.js` into a project-local
|
||||
`assets/` (with `assets/animations/animations.css`), then rewrite the
|
||||
four `<link>`/`<script>` tags in `index.html` from
|
||||
`../../../assets/...` to the matching project-local paths
|
||||
(`assets/fonts.css`, `assets/base.css`,
|
||||
`assets/animations/animations.css`, `assets/runtime.js`).
|
||||
- **Recipe B — inline:** read the same four files and replace each
|
||||
`<link rel="stylesheet" href="../../../assets/...">` with a
|
||||
`<style>...</style>` containing the file's contents, and the
|
||||
`<script src="../../../assets/runtime.js">` with a
|
||||
`<script>...</script>` containing `runtime.js`. Yields a single
|
||||
self-contained `index.html`.
|
||||
Either way, do not ship the upstream `../../../assets/...` URLs
|
||||
verbatim into a project artifact — they only work in-tree.
|
||||
4. **Pick a theme.** Default tokens look fine; if the user wants a different
|
||||
feel, swap in any of the 36 themes from `skills/html-ppt/assets/themes/*.css`
|
||||
via `<link id="theme-link">` and let `T` cycle.
|
||||
5. **Replace demo content, not classes.** The `.tpl-dir-key-nav-minimal` scoped CSS only
|
||||
recognises the structural classes shipped in the template — keep them.
|
||||
6. **Speaker notes go inside `<aside class="notes">` or `<div class="notes">`** — never as visible text on the slide.
|
||||
|
||||
## Attribution
|
||||
|
||||
Visual system, layouts, themes and the runtime keyboard model come from
|
||||
the upstream MIT-licensed [`lewislulu/html-ppt-skill`](https://github.com/lewislulu/html-ppt-skill). The
|
||||
LICENSE file ships at `skills/html-ppt/LICENSE`; please keep it in place when
|
||||
redistributing.
|
||||
366
skills/html-ppt-dir-key-nav-minimal/example.html
Normal file
366
skills/html-ppt-dir-key-nav-minimal/example.html
Normal file
|
|
@ -0,0 +1,366 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Dir-Key Nav Minimal</title>
|
||||
<style>/* html-ppt :: shared webfonts */
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@200;300;400;500;600;700;800;900&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@200;300;400;500;600;700;900&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@300;400;600;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,600;0,800;1,400&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@300;400;500;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Archivo+Black&display=swap');
|
||||
|
||||
</style>
|
||||
<style>/* html-ppt :: base.css — reset + shared tokens + layout primitives */
|
||||
/* Default tokens. Themes in assets/themes/*.css override the :root block. */
|
||||
:root {
|
||||
--bg: #ffffff;
|
||||
--bg-soft: #f7f7f8;
|
||||
--surface: #ffffff;
|
||||
--surface-2: #f2f2f4;
|
||||
--border: rgba(0,0,0,.08);
|
||||
--border-strong: rgba(0,0,0,.16);
|
||||
--text-1: #111216;
|
||||
--text-2: #55596a;
|
||||
--text-3: #8a8f9e;
|
||||
--accent: #3b6cff;
|
||||
--accent-2: #7a5cff;
|
||||
--accent-3: #ff5c8a;
|
||||
--good: #1aaf6c;
|
||||
--warn: #f5a524;
|
||||
--bad: #e0445a;
|
||||
--grad: linear-gradient(135deg,#3b6cff,#7a5cff 55%,#ff5c8a);
|
||||
--grad-soft: linear-gradient(135deg,#eef2ff,#f5ecff 55%,#ffeef5);
|
||||
--radius: 18px;
|
||||
--radius-sm: 12px;
|
||||
--radius-lg: 26px;
|
||||
--shadow: 0 10px 30px rgba(18,24,40,.08), 0 2px 6px rgba(18,24,40,.04);
|
||||
--shadow-lg: 0 24px 60px rgba(18,24,40,.14), 0 6px 16px rgba(18,24,40,.06);
|
||||
--font-sans: 'Inter','Noto Sans SC',-apple-system,BlinkMacSystemFont,Helvetica,Arial,sans-serif;
|
||||
--font-serif: 'Playfair Display','Noto Serif SC',Georgia,serif;
|
||||
--font-mono: 'JetBrains Mono','IBM Plex Mono',SFMono-Regular,Menlo,monospace;
|
||||
--font-display: var(--font-sans);
|
||||
--letter-tight: -.03em;
|
||||
--letter-normal: -.01em;
|
||||
--ease: cubic-bezier(.4,0,.2,1);
|
||||
}
|
||||
|
||||
*,*::before,*::after{box-sizing:border-box}
|
||||
html,body{margin:0;padding:0;background:var(--bg);color:var(--text-1);
|
||||
font-family:var(--font-sans);font-weight:400;line-height:1.6;
|
||||
-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;
|
||||
letter-spacing:var(--letter-normal)}
|
||||
img,svg,video{max-width:100%;display:block}
|
||||
a{color:var(--accent);text-decoration:none}
|
||||
a:hover{text-decoration:underline}
|
||||
code,kbd,pre,samp{font-family:var(--font-mono)}
|
||||
|
||||
/* ================= SLIDE SYSTEM ================= */
|
||||
.deck{position:relative;width:100vw;height:100vh;overflow:hidden;background:var(--bg)}
|
||||
.slide{
|
||||
position:absolute;inset:0;
|
||||
display:flex;flex-direction:column;justify-content:center;
|
||||
padding:72px 96px;
|
||||
box-sizing:border-box;
|
||||
opacity:0;pointer-events:none;
|
||||
transition:opacity .5s var(--ease), transform .5s var(--ease);
|
||||
transform:translateX(30px);
|
||||
overflow:hidden;
|
||||
}
|
||||
.slide.is-active{opacity:1;pointer-events:auto;transform:translateX(0);z-index:2}
|
||||
.slide.is-prev{transform:translateX(-30px)}
|
||||
|
||||
/* single-page standalone (used when a layout file is opened directly) */
|
||||
body.single .slide{position:relative;width:100vw;height:100vh;opacity:1;transform:none;pointer-events:auto}
|
||||
|
||||
/* ================= TYPOGRAPHY ================= */
|
||||
.eyebrow{font-size:13px;font-weight:500;letter-spacing:.16em;text-transform:uppercase;color:var(--text-3)}
|
||||
.kicker{font-size:14px;font-weight:600;color:var(--accent);letter-spacing:.08em;text-transform:uppercase}
|
||||
h1.title,.h1{font-family:var(--font-display);font-size:72px;line-height:1.05;font-weight:800;letter-spacing:var(--letter-tight);margin:0 0 18px;color:var(--text-1)}
|
||||
h2.title,.h2{font-family:var(--font-display);font-size:54px;line-height:1.1;font-weight:700;letter-spacing:var(--letter-tight);margin:0 0 14px}
|
||||
h3,.h3{font-size:32px;line-height:1.2;font-weight:600;letter-spacing:var(--letter-normal);margin:0 0 10px}
|
||||
h4,.h4{font-size:22px;line-height:1.3;font-weight:600;margin:0 0 8px}
|
||||
.lede{font-size:22px;line-height:1.55;color:var(--text-2);font-weight:300;max-width:62ch}
|
||||
.dim{color:var(--text-2)}
|
||||
.dim2{color:var(--text-3)}
|
||||
.mono{font-family:var(--font-mono)}
|
||||
.serif{font-family:var(--font-serif)}
|
||||
.gradient-text{background:var(--grad);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;color:transparent}
|
||||
|
||||
/* ================= LAYOUT PRIMITIVES ================= */
|
||||
.stack>*+*{margin-top:14px}
|
||||
.row{display:flex;gap:24px;align-items:center}
|
||||
.row.wrap{flex-wrap:wrap}
|
||||
.grid{display:grid;gap:24px}
|
||||
.g2{grid-template-columns:repeat(2,1fr)}
|
||||
.g3{grid-template-columns:repeat(3,1fr)}
|
||||
.g4{grid-template-columns:repeat(4,1fr)}
|
||||
.center{display:flex;align-items:center;justify-content:center;text-align:center}
|
||||
.fill{flex:1}
|
||||
.sp-t{padding-top:24px}.sp-b{padding-bottom:24px}
|
||||
.mt-s{margin-top:8px}.mt-m{margin-top:18px}.mt-l{margin-top:32px}
|
||||
.mb-s{margin-bottom:8px}.mb-m{margin-bottom:18px}.mb-l{margin-bottom:32px}
|
||||
|
||||
/* ================= CARDS ================= */
|
||||
.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);
|
||||
padding:26px 28px;box-shadow:var(--shadow);position:relative;overflow:hidden}
|
||||
.card-soft{background:var(--surface-2);border:1px solid var(--border)}
|
||||
.card-outline{background:transparent;border:1.5px solid var(--border-strong);box-shadow:none}
|
||||
.card-accent{background:var(--surface);border-top:3px solid var(--accent)}
|
||||
.card-hover{transition:transform .3s var(--ease),box-shadow .3s var(--ease)}
|
||||
.card-hover:hover{transform:translateY(-4px);box-shadow:var(--shadow-lg)}
|
||||
|
||||
.pill{display:inline-block;padding:4px 12px;border-radius:999px;font-size:12px;font-weight:500;
|
||||
background:var(--surface-2);color:var(--text-2);border:1px solid var(--border)}
|
||||
.pill-accent{background:color-mix(in srgb,var(--accent) 12%,transparent);color:var(--accent);border-color:color-mix(in srgb,var(--accent) 28%,transparent)}
|
||||
|
||||
/* ================= BARS / DIVIDERS ================= */
|
||||
.divider{height:1px;background:var(--border);width:100%}
|
||||
.divider-accent{height:3px;width:72px;background:var(--accent);border-radius:2px}
|
||||
|
||||
/* ================= CHROME (header/footer/progress) ================= */
|
||||
.deck-header{position:absolute;top:24px;left:40px;right:40px;display:flex;align-items:center;justify-content:space-between;
|
||||
font-size:12px;color:var(--text-3);letter-spacing:.12em;text-transform:uppercase;z-index:10;pointer-events:none}
|
||||
.deck-footer{position:absolute;bottom:24px;left:40px;right:40px;display:flex;align-items:center;justify-content:space-between;
|
||||
font-size:12px;color:var(--text-3);z-index:10;pointer-events:none}
|
||||
.slide-number::before{content:attr(data-current)}
|
||||
.slide-number::after{content:" / " attr(data-total)}
|
||||
.progress-bar{position:fixed;left:0;right:0;bottom:0;height:3px;background:transparent;z-index:20}
|
||||
.progress-bar > span{display:block;height:100%;width:0;background:var(--accent);transition:width .3s var(--ease)}
|
||||
|
||||
/* ================= PRESENTER / OVERVIEW ================= */
|
||||
.notes{display:none!important}
|
||||
.notes-overlay{position:fixed;inset:auto 0 0 0;max-height:42vh;background:rgba(20,22,30,.95);color:#e8ebf4;
|
||||
padding:20px 32px;font-size:16px;line-height:1.6;border-top:1px solid rgba(255,255,255,.1);transform:translateY(100%);
|
||||
transition:transform .3s var(--ease);z-index:40;overflow:auto;font-family:var(--font-sans)}
|
||||
.notes-overlay.open{transform:translateY(0)}
|
||||
.overview{position:fixed;inset:0;background:rgba(10,12,18,.92);backdrop-filter:blur(12px);z-index:50;
|
||||
display:none;padding:40px;overflow:auto}
|
||||
.overview.open{display:grid;grid-template-columns:repeat(4,1fr);gap:20px;align-content:start}
|
||||
.overview .thumb{background:var(--surface);border:1px solid var(--border);border-radius:12px;
|
||||
aspect-ratio:16/9;overflow:hidden;cursor:pointer;position:relative;color:var(--text-1);padding:16px;
|
||||
font-size:11px;transition:transform .2s var(--ease)}
|
||||
.overview .thumb:hover{transform:scale(1.04)}
|
||||
.overview .thumb .n{position:absolute;top:8px;left:10px;font-weight:700;font-size:14px;color:var(--text-3)}
|
||||
.overview .thumb .t{position:absolute;bottom:10px;left:14px;right:14px;font-weight:600;color:var(--text-1)}
|
||||
|
||||
/* ================= PRESENTER VIEW ================= */
|
||||
/* Presenter view opens in a separate popup window (S key).
|
||||
* All presenter styles are self-contained in the popup HTML generated by runtime.js.
|
||||
* The audience window (this file) is NOT affected — it stays as normal deck view.
|
||||
* Only the .notes class below is needed to hide speaker notes from audience. */
|
||||
|
||||
/* ================= UTILITY ================= */
|
||||
.hidden{display:none!important}
|
||||
.nowrap{white-space:nowrap}
|
||||
.tr{text-align:right}.tc{text-align:center}.tl{text-align:left}
|
||||
.uppercase{text-transform:uppercase;letter-spacing:.12em}
|
||||
|
||||
/* ================= PRINT ================= */
|
||||
@media print{
|
||||
.slide{position:relative;opacity:1!important;transform:none!important;page-break-after:always;height:100vh}
|
||||
.deck-header,.deck-footer,.progress-bar,.notes-overlay,.overview{display:none!important}
|
||||
}
|
||||
|
||||
</style>
|
||||
<style>/* dir-key-nav-minimal — 方向键极简 · 8 种 mono-background 切换 */
|
||||
.tpl-dir-key-nav-minimal{
|
||||
--dk-font:'Inter','Noto Sans SC','PingFang SC',-apple-system,sans-serif;
|
||||
--dk-mono:'JetBrains Mono',monospace;
|
||||
background:#000;
|
||||
color:#fff;
|
||||
font-family:var(--dk-font);
|
||||
}
|
||||
.tpl-dir-key-nav-minimal .slide{padding:80px 104px;overflow:hidden;position:absolute;inset:0}
|
||||
/* 8 background themes */
|
||||
.tpl-dir-key-nav-minimal .t-indigo{background:linear-gradient(135deg,#0f172a 0%,#1e1b4b 100%);color:#fff}
|
||||
.tpl-dir-key-nav-minimal .t-cream{background:#F5F0E8;color:#1a1a1a}
|
||||
.tpl-dir-key-nav-minimal .t-crimson{background:linear-gradient(135deg,#7f1d1d 0%,#991b1b 100%);color:#fff}
|
||||
.tpl-dir-key-nav-minimal .t-emerald{background:linear-gradient(135deg,#052e16 0%,#064e3b 100%);color:#ecfdf5}
|
||||
.tpl-dir-key-nav-minimal .t-slate{background:linear-gradient(135deg,#0f1923 0%,#1a2942 100%);color:#e6edf3}
|
||||
.tpl-dir-key-nav-minimal .t-violet{background:linear-gradient(135deg,#1e0a2e 0%,#2e1065 100%);color:#f5f3ff}
|
||||
.tpl-dir-key-nav-minimal .t-white{background:#ffffff;color:#111216}
|
||||
.tpl-dir-key-nav-minimal .t-charcoal{background:linear-gradient(135deg,#111827 0%,#1f2937 100%);color:#f3f4f6}
|
||||
|
||||
.tpl-dir-key-nav-minimal .dk-snum{position:absolute;top:30px;right:48px;font-size:11px;font-weight:700;letter-spacing:3px;text-transform:uppercase;font-family:var(--dk-mono)}
|
||||
.tpl-dir-key-nav-minimal .t-cream .dk-snum,
|
||||
.tpl-dir-key-nav-minimal .t-white .dk-snum{color:#999}
|
||||
.tpl-dir-key-nav-minimal .t-indigo .dk-snum,
|
||||
.tpl-dir-key-nav-minimal .t-crimson .dk-snum,
|
||||
.tpl-dir-key-nav-minimal .t-emerald .dk-snum,
|
||||
.tpl-dir-key-nav-minimal .t-slate .dk-snum,
|
||||
.tpl-dir-key-nav-minimal .t-violet .dk-snum,
|
||||
.tpl-dir-key-nav-minimal .t-charcoal .dk-snum{color:rgba(255,255,255,.38)}
|
||||
|
||||
.tpl-dir-key-nav-minimal .dk-eyebrow{font-size:12px;font-weight:700;letter-spacing:3.5px;text-transform:uppercase;opacity:.55;margin-bottom:22px;display:flex;align-items:center;gap:14px}
|
||||
.tpl-dir-key-nav-minimal .dk-eyebrow::after{content:'';flex:1;max-width:120px;height:1px;background:currentColor;opacity:.3}
|
||||
.tpl-dir-key-nav-minimal .dk-h0{font-size:160px;font-weight:900;line-height:.9;letter-spacing:-5px;margin:0 0 20px}
|
||||
.tpl-dir-key-nav-minimal .dk-h1{font-size:100px;font-weight:900;line-height:.98;letter-spacing:-3px;margin:0 0 18px}
|
||||
.tpl-dir-key-nav-minimal .dk-h2{font-size:72px;font-weight:800;line-height:1.05;letter-spacing:-2px;margin:0 0 16px}
|
||||
.tpl-dir-key-nav-minimal .dk-lede{font-size:26px;line-height:1.45;opacity:.72;max-width:900px;font-weight:300}
|
||||
.tpl-dir-key-nav-minimal .dk-lede strong{font-weight:700;opacity:1}
|
||||
.tpl-dir-key-nav-minimal .dk-big{font-family:var(--dk-mono);font-size:240px;font-weight:800;line-height:.9;letter-spacing:-10px}
|
||||
|
||||
.tpl-dir-key-nav-minimal .dk-line{display:block;width:90px;height:4px;background:currentColor;margin:30px 0;opacity:.85}
|
||||
.tpl-dir-key-nav-minimal .t-indigo .dk-accent{color:#a5b4fc}
|
||||
.tpl-dir-key-nav-minimal .t-cream .dk-accent{color:#B5392A}
|
||||
.tpl-dir-key-nav-minimal .t-crimson .dk-accent{color:#fecaca}
|
||||
.tpl-dir-key-nav-minimal .t-emerald .dk-accent{color:#6ee7b7}
|
||||
.tpl-dir-key-nav-minimal .t-slate .dk-accent{color:#7dd3fc}
|
||||
.tpl-dir-key-nav-minimal .t-violet .dk-accent{color:#c4b5fd}
|
||||
.tpl-dir-key-nav-minimal .t-white .dk-accent{color:#6366f1}
|
||||
.tpl-dir-key-nav-minimal .t-charcoal .dk-accent{color:#fbbf24}
|
||||
|
||||
.tpl-dir-key-nav-minimal .dk-list{list-style:none;padding:0;margin:28px 0 0;font-family:var(--dk-mono);font-size:22px;line-height:2}
|
||||
.tpl-dir-key-nav-minimal .dk-list li{padding-left:30px;position:relative;font-weight:400;opacity:.85}
|
||||
.tpl-dir-key-nav-minimal .dk-list li::before{content:'→';position:absolute;left:0;opacity:.5}
|
||||
.tpl-dir-key-nav-minimal .dk-grid-2{display:grid;grid-template-columns:1fr 1fr;gap:56px;margin-top:36px}
|
||||
.tpl-dir-key-nav-minimal .dk-col h3{font-size:28px;font-weight:700;margin-bottom:10px}
|
||||
.tpl-dir-key-nav-minimal .dk-col p{font-size:19px;line-height:1.55;opacity:.72;font-weight:300}
|
||||
.tpl-dir-key-nav-minimal .dk-code{font-family:var(--dk-mono);font-size:16px;line-height:1.9;background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.12);border-radius:10px;padding:24px 28px;margin-top:24px;white-space:pre}
|
||||
.tpl-dir-key-nav-minimal .t-cream .dk-code,
|
||||
.tpl-dir-key-nav-minimal .t-white .dk-code{background:rgba(0,0,0,.05);border-color:rgba(0,0,0,.1)}
|
||||
.tpl-dir-key-nav-minimal .dk-keyhint{position:absolute;bottom:34px;left:104px;font-family:var(--dk-mono);font-size:12px;letter-spacing:2px;text-transform:uppercase;opacity:.45}
|
||||
.tpl-dir-key-nav-minimal .dk-keyhint kbd{display:inline-block;padding:2px 10px;margin:0 3px;border:1px solid currentColor;border-radius:4px;font-size:12px}
|
||||
.tpl-dir-key-nav-minimal .dk-page{position:absolute;bottom:34px;right:48px;font-family:var(--dk-mono);font-size:12px;letter-spacing:2px;opacity:.45}
|
||||
|
||||
</style>
|
||||
<style>
|
||||
/* Static-preview fallback (runtime.js is absent — keep every slide visible) */
|
||||
.deck{height:auto;min-height:100vh;overflow:visible}
|
||||
.slide{position:relative;inset:auto;opacity:1;pointer-events:auto;transform:none;height:100vh;page-break-after:always}
|
||||
.deck-header,.deck-footer,.slide-number,.progress-bar,.notes-overlay,.overview{pointer-events:none}
|
||||
.notes{display:none!important}
|
||||
</style></head>
|
||||
<body class="tpl-dir-key-nav-minimal">
|
||||
<div class="deck">
|
||||
|
||||
<!-- 1. COVER · indigo -->
|
||||
<section class="slide t-indigo is-active">
|
||||
<div class="dk-snum">01 / 08</div>
|
||||
<div style="margin:auto 0">
|
||||
<div class="dk-eyebrow">Karpathy LLM Wiki</div>
|
||||
<h1 class="dk-h0">为什么笔记<br>治不了 <span class="dk-accent">LLM</span></h1>
|
||||
<span class="dk-line"></span>
|
||||
<p class="dk-lede">8 种背景、8 张幻灯,一个关于如何把 AI 变成「长期记忆外挂」的最短陈述。<strong>按 → 继续。</strong></p>
|
||||
</div>
|
||||
<div class="dk-keyhint">nav · <kbd>←</kbd> <kbd>→</kbd> · <kbd>space</kbd></div>
|
||||
<div class="dk-page">cover</div>
|
||||
</section>
|
||||
|
||||
<!-- 2. SECTION · cream -->
|
||||
<section class="slide t-cream">
|
||||
<div class="dk-snum">02 / 08</div>
|
||||
<div style="margin:auto 0">
|
||||
<div class="dk-eyebrow">Chapter 01</div>
|
||||
<h1 class="dk-h0">The <span class="dk-accent">Problem</span>.</h1>
|
||||
<span class="dk-line"></span>
|
||||
<p class="dk-lede">Token 上限是一个物理事实。你每次和 LLM 说话,它都是一个失忆症患者。</p>
|
||||
</div>
|
||||
<div class="dk-keyhint">chapter · 01 / 04</div>
|
||||
<div class="dk-page">section</div>
|
||||
</section>
|
||||
|
||||
<!-- 3. CONTENT · crimson -->
|
||||
<section class="slide t-crimson">
|
||||
<div class="dk-snum">03 / 08</div>
|
||||
<div style="margin:auto 0">
|
||||
<div class="dk-eyebrow">Symptoms</div>
|
||||
<h2 class="dk-h1">四种你已经<br>受够的<br><span class="dk-accent">遗忘</span>。</h2>
|
||||
<ul class="dk-list">
|
||||
<li>昨天聊过的项目,今天重新解释一遍</li>
|
||||
<li>上下文窗口一到,它开始「编造记忆」</li>
|
||||
<li>不同 session 之间毫无关联,就像第一次见</li>
|
||||
<li>你的真正偏好从未被记住,每次都要 re-prompt</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="dk-keyhint">content · list</div>
|
||||
<div class="dk-page">03</div>
|
||||
</section>
|
||||
|
||||
<!-- 4. CONTENT · emerald -->
|
||||
<section class="slide t-emerald">
|
||||
<div class="dk-snum">04 / 08</div>
|
||||
<div style="margin:auto 0">
|
||||
<div class="dk-eyebrow">The Fix</div>
|
||||
<h2 class="dk-h1">答案不是<br><span class="dk-accent">更大</span> 的窗口。</h2>
|
||||
<p class="dk-lede" style="margin-top:10px">而是:把你的知识、偏好、历史都<strong>写进文件系统</strong>。<br>让 LLM 每次对话前,先去读那个系统。</p>
|
||||
<div class="dk-grid-2">
|
||||
<div class="dk-col"><h3>× 窗口 stuffing</h3><p>把所有东西塞 prompt,贵、慢、最终溢出。</p></div>
|
||||
<div class="dk-col"><h3>✓ 文件 + 检索</h3><p>按需加载,永远不溢出,结构化可 diff。</p></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dk-keyhint">content · compare</div>
|
||||
<div class="dk-page">04</div>
|
||||
</section>
|
||||
|
||||
<!-- 5. CODE · slate -->
|
||||
<section class="slide t-slate">
|
||||
<div class="dk-snum">05 / 08</div>
|
||||
<div style="margin:auto 0">
|
||||
<div class="dk-eyebrow">Minimal Setup</div>
|
||||
<h2 class="dk-h2"><span class="dk-accent">4 行</span> YAML<br>就能开始。</h2>
|
||||
<pre class="dk-code">memory:
|
||||
root: ~/.llm-wiki
|
||||
format: markdown
|
||||
retrieval: hybrid # embedding + bm25</pre>
|
||||
<p class="dk-lede" style="margin-top:16px;font-size:20px">你现在拥有一个会随时间增长的 <strong>第二大脑</strong>。每次对话它都会被读、被更新。</p>
|
||||
</div>
|
||||
<div class="dk-keyhint">content · code</div>
|
||||
<div class="dk-page">05</div>
|
||||
</section>
|
||||
|
||||
<!-- 6. CHART · violet — big number with bar -->
|
||||
<section class="slide t-violet">
|
||||
<div class="dk-snum">06 / 08</div>
|
||||
<div style="margin:auto 0">
|
||||
<div class="dk-eyebrow">30-day result</div>
|
||||
<div class="dk-big dk-accent">87%</div>
|
||||
<p class="dk-lede" style="margin-top:14px;font-size:26px">的 re-explain 被消除。平均每次对话节省 <strong>4.2 分钟</strong> 的 re-context。</p>
|
||||
<svg viewBox="0 0 900 80" style="width:100%;max-width:900px;margin-top:30px">
|
||||
<rect x="0" y="30" width="900" height="22" rx="11" fill="rgba(255,255,255,.12)"/>
|
||||
<rect x="0" y="30" width="783" height="22" rx="11" fill="#c4b5fd"/>
|
||||
<text x="792" y="47" font-family="JetBrains Mono" font-size="16" fill="#c4b5fd" font-weight="700">87%</text>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="dk-keyhint">chart · big-num</div>
|
||||
<div class="dk-page">06</div>
|
||||
</section>
|
||||
|
||||
<!-- 7. CTA · white -->
|
||||
<section class="slide t-white">
|
||||
<div class="dk-snum">07 / 08</div>
|
||||
<div style="margin:auto 0">
|
||||
<div class="dk-eyebrow">Start tonight</div>
|
||||
<h2 class="dk-h1">开始<br>你的 <span class="dk-accent">wiki</span>。</h2>
|
||||
<span class="dk-line"></span>
|
||||
<p class="dk-lede">不是装又一个插件。是决定:从今晚起,<strong>你的所有 AI 对话都要有一个共同的 vault</strong>。</p>
|
||||
<pre class="dk-code" style="font-size:18px">$ mkdir ~/llm-wiki && cd ~/llm-wiki
|
||||
$ git init
|
||||
$ echo "# my brain" > README.md</pre>
|
||||
</div>
|
||||
<div class="dk-keyhint">cta · three-commands</div>
|
||||
<div class="dk-page">07</div>
|
||||
</section>
|
||||
|
||||
<!-- 8. THANKS · charcoal -->
|
||||
<section class="slide t-charcoal">
|
||||
<div class="dk-snum">08 / 08</div>
|
||||
<div style="margin:auto 0">
|
||||
<div class="dk-eyebrow">End · thanks for staying</div>
|
||||
<h1 class="dk-h0"><span class="dk-accent">謝謝</span>。</h1>
|
||||
<span class="dk-line"></span>
|
||||
<p class="dk-lede">Karpathy 的原始 thread + 我的 vault 结构都在 <strong>github.com/lewis/llm-wiki</strong>。欢迎按 ← 再看一遍。</p>
|
||||
</div>
|
||||
<div class="dk-keyhint">press <kbd>←</kbd> to rewind · <kbd>F</kbd> for fullscreen</div>
|
||||
<div class="dk-page">fin</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
78
skills/html-ppt-graphify-dark-graph/SKILL.md
Normal file
78
skills/html-ppt-graphify-dark-graph/SKILL.md
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
---
|
||||
name: html-ppt-graphify-dark-graph
|
||||
description: 暗底知识图谱 deck — #06060c→#0e1020 深夜渐变 + 漂浮 blur orbs、封面 SVG 力导向图谱、彩虹渐变标题、JetBrains Mono 命令行高亮、glass-morphism 卡片。适合 dev-tool / CLI / 知识图谱 / 数据可视化的发布会,"AI-native + 科幻 + 暖色" 调子。
|
||||
triggers:
|
||||
- "知识图谱"
|
||||
- "graph deck"
|
||||
- "dark graph"
|
||||
- "dev tool launch"
|
||||
- "cli launch"
|
||||
- "data viz launch"
|
||||
od:
|
||||
mode: deck
|
||||
scenario: engineering
|
||||
featured: 28
|
||||
upstream: "https://github.com/lewislulu/html-ppt-skill"
|
||||
preview:
|
||||
type: html
|
||||
entry: index.html
|
||||
design_system:
|
||||
requires: false
|
||||
speaker_notes: true
|
||||
animations: true
|
||||
example_prompt: "用 html-ppt-graphify-dark-graph 模板做一份 dev-tool 发布会 PPT。深夜渐变背景 + 力导向图谱封面 + 彩虹标题 + JetBrains Mono 命令行。先确认:工具名、核心能力、demo 步骤;要不要现场敲 CLI。"
|
||||
---
|
||||
# HTML PPT · 暗底知识图谱
|
||||
|
||||
A focused entry point into the [`html-ppt`](../html-ppt/SKILL.md) master skill that lands the user directly on the **`graphify-dark-graph`** full-deck template.
|
||||
|
||||
## When this card is picked
|
||||
|
||||
The Examples gallery wires "Use this prompt" to the example_prompt above. When you accept that prompt, this card is the right pick if the user wants exactly the visual identity of `graphify-dark-graph` (see the upstream [full-decks catalog](../html-ppt/references/full-decks.md) for screenshots and rationale).
|
||||
|
||||
## How to author the deck
|
||||
|
||||
1. **Read the master skill first.** All authoring rules live in
|
||||
[`skills/html-ppt/SKILL.md`](../html-ppt/SKILL.md) — content/audience checklist,
|
||||
token rules, layout reuse, presenter mode, the keyboard runtime, and the
|
||||
"never put presenter-only text on the slide" rule.
|
||||
2. **Start from the matching template folder:**
|
||||
`skills/html-ppt/templates/full-decks/graphify-dark-graph/` — copy `index.html` and
|
||||
`style.css` into the project, keep the `.tpl-graphify-dark-graph` body class.
|
||||
3. **Bring the shared runtime with the template.** The upstream
|
||||
`index.html` links the shared CSS/JS via `../../../assets/...` because it
|
||||
sits three folders deep inside `skills/html-ppt/templates/full-decks/`.
|
||||
Once you copy `index.html` into the project, those parent-relative URLs
|
||||
no longer resolve and `base.css`, `animations.css`, and `runtime.js`
|
||||
will 404 — meaning the deck never activates and slide navigation is
|
||||
dead. Pick one of these two recipes per project:
|
||||
- **Recipe A — copy + rewrite (preferred):** copy
|
||||
`skills/html-ppt/assets/fonts.css`, `skills/html-ppt/assets/base.css`,
|
||||
`skills/html-ppt/assets/animations/animations.css`, and
|
||||
`skills/html-ppt/assets/runtime.js` into a project-local
|
||||
`assets/` (with `assets/animations/animations.css`), then rewrite the
|
||||
four `<link>`/`<script>` tags in `index.html` from
|
||||
`../../../assets/...` to the matching project-local paths
|
||||
(`assets/fonts.css`, `assets/base.css`,
|
||||
`assets/animations/animations.css`, `assets/runtime.js`).
|
||||
- **Recipe B — inline:** read the same four files and replace each
|
||||
`<link rel="stylesheet" href="../../../assets/...">` with a
|
||||
`<style>...</style>` containing the file's contents, and the
|
||||
`<script src="../../../assets/runtime.js">` with a
|
||||
`<script>...</script>` containing `runtime.js`. Yields a single
|
||||
self-contained `index.html`.
|
||||
Either way, do not ship the upstream `../../../assets/...` URLs
|
||||
verbatim into a project artifact — they only work in-tree.
|
||||
4. **Pick a theme.** Default tokens look fine; if the user wants a different
|
||||
feel, swap in any of the 36 themes from `skills/html-ppt/assets/themes/*.css`
|
||||
via `<link id="theme-link">` and let `T` cycle.
|
||||
5. **Replace demo content, not classes.** The `.tpl-graphify-dark-graph` scoped CSS only
|
||||
recognises the structural classes shipped in the template — keep them.
|
||||
6. **Speaker notes go inside `<aside class="notes">` or `<div class="notes">`** — never as visible text on the slide.
|
||||
|
||||
## Attribution
|
||||
|
||||
Visual system, layouts, themes and the runtime keyboard model come from
|
||||
the upstream MIT-licensed [`lewislulu/html-ppt-skill`](https://github.com/lewislulu/html-ppt-skill). The
|
||||
LICENSE file ships at `skills/html-ppt/LICENSE`; please keep it in place when
|
||||
redistributing.
|
||||
402
skills/html-ppt-graphify-dark-graph/example.html
Normal file
402
skills/html-ppt-graphify-dark-graph/example.html
Normal file
|
|
@ -0,0 +1,402 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Graphify Dark Graph</title>
|
||||
<style>/* html-ppt :: shared webfonts */
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@200;300;400;500;600;700;800;900&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@200;300;400;500;600;700;900&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@300;400;600;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,600;0,800;1,400&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@300;400;500;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Archivo+Black&display=swap');
|
||||
|
||||
</style>
|
||||
<style>/* html-ppt :: base.css — reset + shared tokens + layout primitives */
|
||||
/* Default tokens. Themes in assets/themes/*.css override the :root block. */
|
||||
:root {
|
||||
--bg: #ffffff;
|
||||
--bg-soft: #f7f7f8;
|
||||
--surface: #ffffff;
|
||||
--surface-2: #f2f2f4;
|
||||
--border: rgba(0,0,0,.08);
|
||||
--border-strong: rgba(0,0,0,.16);
|
||||
--text-1: #111216;
|
||||
--text-2: #55596a;
|
||||
--text-3: #8a8f9e;
|
||||
--accent: #3b6cff;
|
||||
--accent-2: #7a5cff;
|
||||
--accent-3: #ff5c8a;
|
||||
--good: #1aaf6c;
|
||||
--warn: #f5a524;
|
||||
--bad: #e0445a;
|
||||
--grad: linear-gradient(135deg,#3b6cff,#7a5cff 55%,#ff5c8a);
|
||||
--grad-soft: linear-gradient(135deg,#eef2ff,#f5ecff 55%,#ffeef5);
|
||||
--radius: 18px;
|
||||
--radius-sm: 12px;
|
||||
--radius-lg: 26px;
|
||||
--shadow: 0 10px 30px rgba(18,24,40,.08), 0 2px 6px rgba(18,24,40,.04);
|
||||
--shadow-lg: 0 24px 60px rgba(18,24,40,.14), 0 6px 16px rgba(18,24,40,.06);
|
||||
--font-sans: 'Inter','Noto Sans SC',-apple-system,BlinkMacSystemFont,Helvetica,Arial,sans-serif;
|
||||
--font-serif: 'Playfair Display','Noto Serif SC',Georgia,serif;
|
||||
--font-mono: 'JetBrains Mono','IBM Plex Mono',SFMono-Regular,Menlo,monospace;
|
||||
--font-display: var(--font-sans);
|
||||
--letter-tight: -.03em;
|
||||
--letter-normal: -.01em;
|
||||
--ease: cubic-bezier(.4,0,.2,1);
|
||||
}
|
||||
|
||||
*,*::before,*::after{box-sizing:border-box}
|
||||
html,body{margin:0;padding:0;background:var(--bg);color:var(--text-1);
|
||||
font-family:var(--font-sans);font-weight:400;line-height:1.6;
|
||||
-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;
|
||||
letter-spacing:var(--letter-normal)}
|
||||
img,svg,video{max-width:100%;display:block}
|
||||
a{color:var(--accent);text-decoration:none}
|
||||
a:hover{text-decoration:underline}
|
||||
code,kbd,pre,samp{font-family:var(--font-mono)}
|
||||
|
||||
/* ================= SLIDE SYSTEM ================= */
|
||||
.deck{position:relative;width:100vw;height:100vh;overflow:hidden;background:var(--bg)}
|
||||
.slide{
|
||||
position:absolute;inset:0;
|
||||
display:flex;flex-direction:column;justify-content:center;
|
||||
padding:72px 96px;
|
||||
box-sizing:border-box;
|
||||
opacity:0;pointer-events:none;
|
||||
transition:opacity .5s var(--ease), transform .5s var(--ease);
|
||||
transform:translateX(30px);
|
||||
overflow:hidden;
|
||||
}
|
||||
.slide.is-active{opacity:1;pointer-events:auto;transform:translateX(0);z-index:2}
|
||||
.slide.is-prev{transform:translateX(-30px)}
|
||||
|
||||
/* single-page standalone (used when a layout file is opened directly) */
|
||||
body.single .slide{position:relative;width:100vw;height:100vh;opacity:1;transform:none;pointer-events:auto}
|
||||
|
||||
/* ================= TYPOGRAPHY ================= */
|
||||
.eyebrow{font-size:13px;font-weight:500;letter-spacing:.16em;text-transform:uppercase;color:var(--text-3)}
|
||||
.kicker{font-size:14px;font-weight:600;color:var(--accent);letter-spacing:.08em;text-transform:uppercase}
|
||||
h1.title,.h1{font-family:var(--font-display);font-size:72px;line-height:1.05;font-weight:800;letter-spacing:var(--letter-tight);margin:0 0 18px;color:var(--text-1)}
|
||||
h2.title,.h2{font-family:var(--font-display);font-size:54px;line-height:1.1;font-weight:700;letter-spacing:var(--letter-tight);margin:0 0 14px}
|
||||
h3,.h3{font-size:32px;line-height:1.2;font-weight:600;letter-spacing:var(--letter-normal);margin:0 0 10px}
|
||||
h4,.h4{font-size:22px;line-height:1.3;font-weight:600;margin:0 0 8px}
|
||||
.lede{font-size:22px;line-height:1.55;color:var(--text-2);font-weight:300;max-width:62ch}
|
||||
.dim{color:var(--text-2)}
|
||||
.dim2{color:var(--text-3)}
|
||||
.mono{font-family:var(--font-mono)}
|
||||
.serif{font-family:var(--font-serif)}
|
||||
.gradient-text{background:var(--grad);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;color:transparent}
|
||||
|
||||
/* ================= LAYOUT PRIMITIVES ================= */
|
||||
.stack>*+*{margin-top:14px}
|
||||
.row{display:flex;gap:24px;align-items:center}
|
||||
.row.wrap{flex-wrap:wrap}
|
||||
.grid{display:grid;gap:24px}
|
||||
.g2{grid-template-columns:repeat(2,1fr)}
|
||||
.g3{grid-template-columns:repeat(3,1fr)}
|
||||
.g4{grid-template-columns:repeat(4,1fr)}
|
||||
.center{display:flex;align-items:center;justify-content:center;text-align:center}
|
||||
.fill{flex:1}
|
||||
.sp-t{padding-top:24px}.sp-b{padding-bottom:24px}
|
||||
.mt-s{margin-top:8px}.mt-m{margin-top:18px}.mt-l{margin-top:32px}
|
||||
.mb-s{margin-bottom:8px}.mb-m{margin-bottom:18px}.mb-l{margin-bottom:32px}
|
||||
|
||||
/* ================= CARDS ================= */
|
||||
.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);
|
||||
padding:26px 28px;box-shadow:var(--shadow);position:relative;overflow:hidden}
|
||||
.card-soft{background:var(--surface-2);border:1px solid var(--border)}
|
||||
.card-outline{background:transparent;border:1.5px solid var(--border-strong);box-shadow:none}
|
||||
.card-accent{background:var(--surface);border-top:3px solid var(--accent)}
|
||||
.card-hover{transition:transform .3s var(--ease),box-shadow .3s var(--ease)}
|
||||
.card-hover:hover{transform:translateY(-4px);box-shadow:var(--shadow-lg)}
|
||||
|
||||
.pill{display:inline-block;padding:4px 12px;border-radius:999px;font-size:12px;font-weight:500;
|
||||
background:var(--surface-2);color:var(--text-2);border:1px solid var(--border)}
|
||||
.pill-accent{background:color-mix(in srgb,var(--accent) 12%,transparent);color:var(--accent);border-color:color-mix(in srgb,var(--accent) 28%,transparent)}
|
||||
|
||||
/* ================= BARS / DIVIDERS ================= */
|
||||
.divider{height:1px;background:var(--border);width:100%}
|
||||
.divider-accent{height:3px;width:72px;background:var(--accent);border-radius:2px}
|
||||
|
||||
/* ================= CHROME (header/footer/progress) ================= */
|
||||
.deck-header{position:absolute;top:24px;left:40px;right:40px;display:flex;align-items:center;justify-content:space-between;
|
||||
font-size:12px;color:var(--text-3);letter-spacing:.12em;text-transform:uppercase;z-index:10;pointer-events:none}
|
||||
.deck-footer{position:absolute;bottom:24px;left:40px;right:40px;display:flex;align-items:center;justify-content:space-between;
|
||||
font-size:12px;color:var(--text-3);z-index:10;pointer-events:none}
|
||||
.slide-number::before{content:attr(data-current)}
|
||||
.slide-number::after{content:" / " attr(data-total)}
|
||||
.progress-bar{position:fixed;left:0;right:0;bottom:0;height:3px;background:transparent;z-index:20}
|
||||
.progress-bar > span{display:block;height:100%;width:0;background:var(--accent);transition:width .3s var(--ease)}
|
||||
|
||||
/* ================= PRESENTER / OVERVIEW ================= */
|
||||
.notes{display:none!important}
|
||||
.notes-overlay{position:fixed;inset:auto 0 0 0;max-height:42vh;background:rgba(20,22,30,.95);color:#e8ebf4;
|
||||
padding:20px 32px;font-size:16px;line-height:1.6;border-top:1px solid rgba(255,255,255,.1);transform:translateY(100%);
|
||||
transition:transform .3s var(--ease);z-index:40;overflow:auto;font-family:var(--font-sans)}
|
||||
.notes-overlay.open{transform:translateY(0)}
|
||||
.overview{position:fixed;inset:0;background:rgba(10,12,18,.92);backdrop-filter:blur(12px);z-index:50;
|
||||
display:none;padding:40px;overflow:auto}
|
||||
.overview.open{display:grid;grid-template-columns:repeat(4,1fr);gap:20px;align-content:start}
|
||||
.overview .thumb{background:var(--surface);border:1px solid var(--border);border-radius:12px;
|
||||
aspect-ratio:16/9;overflow:hidden;cursor:pointer;position:relative;color:var(--text-1);padding:16px;
|
||||
font-size:11px;transition:transform .2s var(--ease)}
|
||||
.overview .thumb:hover{transform:scale(1.04)}
|
||||
.overview .thumb .n{position:absolute;top:8px;left:10px;font-weight:700;font-size:14px;color:var(--text-3)}
|
||||
.overview .thumb .t{position:absolute;bottom:10px;left:14px;right:14px;font-weight:600;color:var(--text-1)}
|
||||
|
||||
/* ================= PRESENTER VIEW ================= */
|
||||
/* Presenter view opens in a separate popup window (S key).
|
||||
* All presenter styles are self-contained in the popup HTML generated by runtime.js.
|
||||
* The audience window (this file) is NOT affected — it stays as normal deck view.
|
||||
* Only the .notes class below is needed to hide speaker notes from audience. */
|
||||
|
||||
/* ================= UTILITY ================= */
|
||||
.hidden{display:none!important}
|
||||
.nowrap{white-space:nowrap}
|
||||
.tr{text-align:right}.tc{text-align:center}.tl{text-align:left}
|
||||
.uppercase{text-transform:uppercase;letter-spacing:.12em}
|
||||
|
||||
/* ================= PRINT ================= */
|
||||
@media print{
|
||||
.slide{position:relative;opacity:1!important;transform:none!important;page-break-after:always;height:100vh}
|
||||
.deck-header,.deck-footer,.progress-bar,.notes-overlay,.overview{display:none!important}
|
||||
}
|
||||
|
||||
</style>
|
||||
<style>/* graphify-dark-graph — 暗底玻璃 + 力导向知识图谱 */
|
||||
.tpl-graphify-dark-graph{
|
||||
--gd-bg:#06060c;
|
||||
--gd-bg2:#0e1020;
|
||||
--gd-text:#f0ece4;
|
||||
--gd-text2:#b0a99e;
|
||||
--gd-text3:#7a746c;
|
||||
--gd-warm:#e8a87c;
|
||||
--gd-blue:#7eb8da;
|
||||
--gd-green:#7ed3a4;
|
||||
--gd-rose:#d4a0b9;
|
||||
--gd-purple:#b8a4d6;
|
||||
--gd-danger:#e07070;
|
||||
background:var(--gd-bg);
|
||||
color:var(--gd-text);
|
||||
font-family:'Inter','Noto Sans SC',-apple-system,sans-serif;
|
||||
letter-spacing:-.01em;
|
||||
}
|
||||
.tpl-graphify-dark-graph .slide{background:linear-gradient(160deg,#08080f,#0e1020 50%,#08080f);color:var(--gd-text);padding:64px 88px;overflow:hidden}
|
||||
.tpl-graphify-dark-graph .gd-ambient{position:absolute;inset:0;pointer-events:none;z-index:0;overflow:hidden}
|
||||
.tpl-graphify-dark-graph .gd-orb{position:absolute;border-radius:50%;filter:blur(110px);opacity:.35;animation:gdDrift 22s ease-in-out infinite alternate}
|
||||
.tpl-graphify-dark-graph .gd-orb-1{width:520px;height:520px;background:radial-gradient(circle,rgba(126,184,218,.55),transparent 70%);top:-12%;left:-6%}
|
||||
.tpl-graphify-dark-graph .gd-orb-2{width:460px;height:460px;background:radial-gradient(circle,rgba(232,168,124,.45),transparent 70%);top:55%;right:-8%;animation-delay:-6s}
|
||||
.tpl-graphify-dark-graph .gd-orb-3{width:420px;height:420px;background:radial-gradient(circle,rgba(184,164,214,.4),transparent 70%);bottom:-8%;left:30%;animation-delay:-11s}
|
||||
@keyframes gdDrift{0%{transform:translate(0,0) scale(1)}100%{transform:translate(25px,-20px) scale(1.08)}}
|
||||
.tpl-graphify-dark-graph .slide > *{position:relative;z-index:2}
|
||||
.tpl-graphify-dark-graph .gd-snum{position:absolute;top:28px;right:40px;font-size:12px;letter-spacing:.25em;color:var(--gd-text3);z-index:3}
|
||||
.tpl-graphify-dark-graph .gd-eyebrow{font-size:13px;letter-spacing:.2em;text-transform:uppercase;color:var(--gd-text3);font-weight:500}
|
||||
.tpl-graphify-dark-graph .gd-h1{font-size:74px;font-weight:800;line-height:1.08;letter-spacing:-.02em;margin:16px 0 10px;color:var(--gd-text)}
|
||||
.tpl-graphify-dark-graph .gd-h2{font-size:52px;font-weight:700;line-height:1.12;margin:0 0 14px}
|
||||
.tpl-graphify-dark-graph .gd-lede{font-size:22px;line-height:1.65;font-weight:300;color:var(--gd-text2);max-width:850px}
|
||||
.tpl-graphify-dark-graph .gd-rainbow{background:linear-gradient(90deg,#ff0080,#ff4d00,#ff9900,#ffe600,#00c853,#0091ea,#6200ea,#ff0080);background-size:200% auto;-webkit-background-clip:text;-webkit-text-fill-color:transparent;animation:gdRainbow 4s linear infinite}
|
||||
@keyframes gdRainbow{0%{background-position:0% center}100%{background-position:200% center}}
|
||||
.tpl-graphify-dark-graph .gd-grad{background:linear-gradient(135deg,var(--gd-warm),var(--gd-rose),var(--gd-purple));-webkit-background-clip:text;-webkit-text-fill-color:transparent}
|
||||
.tpl-graphify-dark-graph .gd-accent{color:var(--gd-warm);font-weight:500}
|
||||
.tpl-graphify-dark-graph .gd-green{color:var(--gd-green)}
|
||||
.tpl-graphify-dark-graph .gd-blue{color:var(--gd-blue)}
|
||||
.tpl-graphify-dark-graph .gd-dim{color:var(--gd-text2)}
|
||||
.tpl-graphify-dark-graph .gd-mono{font-family:'JetBrains Mono',monospace}
|
||||
.tpl-graphify-dark-graph .gd-glass{position:relative;overflow:hidden;border-radius:20px;padding:22px 26px;background:rgba(255,255,255,.03);border:1px solid rgba(255,255,255,.1);backdrop-filter:blur(20px) saturate(160%);box-shadow:0 8px 32px rgba(0,0,0,.3),inset 0 1px 0 rgba(255,255,255,.08)}
|
||||
.tpl-graphify-dark-graph .gd-glass::before{content:'';position:absolute;top:0;left:0;right:0;height:50%;background:linear-gradient(180deg,rgba(255,255,255,.05),transparent);pointer-events:none}
|
||||
.tpl-graphify-dark-graph .gd-glass-warm{background:rgba(232,168,124,.06);border-color:rgba(232,168,124,.2)}
|
||||
.tpl-graphify-dark-graph .gd-glass-green{background:rgba(126,211,164,.06);border-color:rgba(126,211,164,.2)}
|
||||
.tpl-graphify-dark-graph .gd-glass-blue{background:rgba(126,184,218,.06);border-color:rgba(126,184,218,.2)}
|
||||
.tpl-graphify-dark-graph .gd-grid-3{display:grid;grid-template-columns:repeat(3,1fr);gap:16px;margin-top:24px}
|
||||
.tpl-graphify-dark-graph .gd-grid-4{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;margin-top:24px}
|
||||
.tpl-graphify-dark-graph .gd-tag{display:inline-block;border-radius:999px;padding:5px 14px;font-size:12px;font-weight:500;margin:2px 4px 2px 0;background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.1);color:var(--gd-text2)}
|
||||
.tpl-graphify-dark-graph .gd-cmd{font-family:'JetBrains Mono',monospace;font-size:32px;font-weight:700;color:var(--gd-green);text-shadow:0 0 30px rgba(126,211,164,.45),0 0 60px rgba(126,211,164,.15);letter-spacing:-.01em}
|
||||
.tpl-graphify-dark-graph .gd-big{font-size:120px;font-weight:900;letter-spacing:-.04em;line-height:1}
|
||||
.tpl-graphify-dark-graph .gd-codebox{background:rgba(0,0,0,.55);border:1px solid rgba(255,255,255,.08);border-radius:14px;padding:22px 26px;font-family:'JetBrains Mono',monospace;font-size:14px;line-height:1.8;color:#d8d4c8}
|
||||
.tpl-graphify-dark-graph .gd-codebox .cm{color:#6b6a62}
|
||||
.tpl-graphify-dark-graph .gd-codebox .kw{color:var(--gd-warm)}
|
||||
.tpl-graphify-dark-graph .gd-codebox .st{color:var(--gd-green)}
|
||||
.tpl-graphify-dark-graph .gd-codebox .fn{color:var(--gd-blue)}
|
||||
|
||||
</style>
|
||||
<style>
|
||||
/* Static-preview fallback (runtime.js is absent — keep every slide visible) */
|
||||
.deck{height:auto;min-height:100vh;overflow:visible}
|
||||
.slide{position:relative;inset:auto;opacity:1;pointer-events:auto;transform:none;height:100vh;page-break-after:always}
|
||||
.deck-header,.deck-footer,.slide-number,.progress-bar,.notes-overlay,.overview{pointer-events:none}
|
||||
.notes{display:none!important}
|
||||
</style></head>
|
||||
<body class="tpl-graphify-dark-graph">
|
||||
<div class="deck">
|
||||
|
||||
<!-- 1. COVER -->
|
||||
<section class="slide is-active">
|
||||
<div class="gd-ambient"><div class="gd-orb gd-orb-1"></div><div class="gd-orb gd-orb-2"></div><div class="gd-orb gd-orb-3"></div></div>
|
||||
<!-- live force-directed graph bg -->
|
||||
<svg viewBox="0 0 1600 900" style="position:absolute;inset:0;width:100%;height:100%;opacity:.38;z-index:1" xmlns="http://www.w3.org/2000/svg">
|
||||
<g stroke="#7eb8da" stroke-width="1" stroke-opacity=".5" fill="none">
|
||||
<line x1="300" y1="200" x2="520" y2="340"/>
|
||||
<line x1="520" y1="340" x2="780" y2="260"/>
|
||||
<line x1="780" y1="260" x2="1040" y2="420"/>
|
||||
<line x1="520" y1="340" x2="640" y2="560"/>
|
||||
<line x1="640" y1="560" x2="900" y2="620"/>
|
||||
<line x1="900" y1="620" x2="1040" y2="420"/>
|
||||
<line x1="1040" y1="420" x2="1260" y2="300"/>
|
||||
<line x1="1260" y1="300" x2="1380" y2="500"/>
|
||||
<line x1="900" y1="620" x2="1120" y2="720"/>
|
||||
<line x1="300" y1="200" x2="200" y2="420"/>
|
||||
<line x1="200" y1="420" x2="360" y2="640"/>
|
||||
<line x1="360" y1="640" x2="640" y2="560"/>
|
||||
</g>
|
||||
<g>
|
||||
<circle cx="300" cy="200" r="10" fill="#e8a87c"/>
|
||||
<circle cx="520" cy="340" r="14" fill="#7eb8da"/>
|
||||
<circle cx="780" cy="260" r="9" fill="#7ed3a4"/>
|
||||
<circle cx="1040" cy="420" r="18" fill="#b8a4d6"/>
|
||||
<circle cx="640" cy="560" r="11" fill="#d4a0b9"/>
|
||||
<circle cx="900" cy="620" r="12" fill="#e8a87c"/>
|
||||
<circle cx="1260" cy="300" r="8" fill="#7ed3a4"/>
|
||||
<circle cx="1380" cy="500" r="10" fill="#7eb8da"/>
|
||||
<circle cx="1120" cy="720" r="9" fill="#d4a0b9"/>
|
||||
<circle cx="200" cy="420" r="8" fill="#b8a4d6"/>
|
||||
<circle cx="360" cy="640" r="11" fill="#7eb8da"/>
|
||||
</g>
|
||||
</svg>
|
||||
<div class="gd-snum">01 / 08</div>
|
||||
<div style="margin-top:auto">
|
||||
<p class="gd-eyebrow">Tech Sharing · 纯干货</p>
|
||||
<h1 class="gd-h1" style="font-size:88px"><span class="gd-rainbow">手把手用 Graphify<br>搭建个人知识图谱</span></h1>
|
||||
<p class="gd-lede" style="margin-top:20px">一行命令 · 全多模态 · 诚实审计 —— <span class="gd-accent">把任何文件夹变成可导航的知识网络。</span></p>
|
||||
<p class="gd-eyebrow" style="margin-top:26px">↑ 背景就是 Graphify 真实跑出来的知识图谱</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 2. SECTION DIVIDER -->
|
||||
<section class="slide">
|
||||
<div class="gd-ambient"><div class="gd-orb gd-orb-1"></div><div class="gd-orb gd-orb-2"></div></div>
|
||||
<div class="gd-snum">02 / 08</div>
|
||||
<div style="margin:auto 0">
|
||||
<div class="gd-eyebrow">Part 01</div>
|
||||
<h1 class="gd-h1" style="font-size:120px">Why <span class="gd-grad">Graph</span>?</h1>
|
||||
<p class="gd-lede">folder → tree → graph,人类认知的下一步</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 3. CONTENT — plugin grid -->
|
||||
<section class="slide">
|
||||
<div class="gd-ambient"><div class="gd-orb gd-orb-2"></div><div class="gd-orb gd-orb-3"></div></div>
|
||||
<div class="gd-snum">03 / 08</div>
|
||||
<p class="gd-eyebrow">Feature Map</p>
|
||||
<h2 class="gd-h2">一个工具,<span class="gd-grad">四件事</span></h2>
|
||||
<div class="gd-grid-4">
|
||||
<div class="gd-glass gd-glass-warm"><div style="font-size:30px">📂</div><h4 style="margin:10px 0 6px">Folder Ingest</h4><p class="gd-dim" style="font-size:13px;line-height:1.55">递归扫描任意路径,支持 md / pdf / 代码 / 图片</p></div>
|
||||
<div class="gd-glass gd-glass-blue"><div style="font-size:30px">🧠</div><h4 style="margin:10px 0 6px">Entity Extract</h4><p class="gd-dim" style="font-size:13px;line-height:1.55">用 LLM 抽概念、人物、事件、关系</p></div>
|
||||
<div class="gd-glass gd-glass-green"><div style="font-size:30px">🕸️</div><h4 style="margin:10px 0 6px">Force Graph</h4><p class="gd-dim" style="font-size:13px;line-height:1.55">D3 力导向,点击即跳转原文</p></div>
|
||||
<div class="gd-glass"><div style="font-size:30px">🔍</div><h4 style="margin:10px 0 6px">Audit Trail</h4><p class="gd-dim" style="font-size:13px;line-height:1.55">每条边都能追溯到 source span</p></div>
|
||||
</div>
|
||||
<div class="gd-glass gd-glass-warm" style="margin-top:24px"><p style="font-size:18px;line-height:1.6">它不是「又一个 RAG」—— 它是 <span class="gd-accent">把检索结果画出来,让你一眼就知道信息长什么样</span>。</p></div>
|
||||
</section>
|
||||
|
||||
<!-- 4. CODE -->
|
||||
<section class="slide">
|
||||
<div class="gd-ambient"><div class="gd-orb gd-orb-1"></div></div>
|
||||
<div class="gd-snum">04 / 08</div>
|
||||
<p class="gd-eyebrow">One command</p>
|
||||
<h2 class="gd-h2">从 0 到图谱,<span class="gd-grad">大概 90 秒</span></h2>
|
||||
<p class="gd-cmd" style="margin:16px 0 22px">$ graphify ~/notes --out ./graph</p>
|
||||
<pre class="gd-codebox"><span class="cm"># graphify.config.yaml</span>
|
||||
<span class="kw">ingest</span>:
|
||||
paths: [<span class="st">~/notes</span>, <span class="st">~/code/docs</span>]
|
||||
include: [<span class="st">"*.md"</span>, <span class="st">"*.pdf"</span>, <span class="st">"*.py"</span>]
|
||||
|
||||
<span class="kw">extract</span>:
|
||||
model: <span class="st">claude-opus-4-6</span>
|
||||
schema: [<span class="st">concept</span>, <span class="st">person</span>, <span class="st">event</span>, <span class="st">relation</span>]
|
||||
|
||||
<span class="kw">render</span>:
|
||||
engine: <span class="st">d3-force</span>
|
||||
audit: <span class="fn">true</span> <span class="cm"># 每条边带 source span</span></pre>
|
||||
</section>
|
||||
|
||||
<!-- 5. CHART — race diagram -->
|
||||
<section class="slide">
|
||||
<div class="gd-ambient"><div class="gd-orb gd-orb-3"></div></div>
|
||||
<div class="gd-snum">05 / 08</div>
|
||||
<p class="gd-eyebrow">Efficiency Race</p>
|
||||
<h2 class="gd-h2">没有知识库 vs 有知识库</h2>
|
||||
<div style="max-width:900px;margin-top:30px">
|
||||
<div style="display:flex;align-items:center;gap:16px;margin-bottom:20px">
|
||||
<div style="width:110px;text-align:right;font-weight:700;color:var(--gd-danger)">没有<br>知识库</div>
|
||||
<div style="flex:1;position:relative;height:70px;background:rgba(224,112,112,.06);border:1px solid rgba(224,112,112,.2);border-radius:16px">
|
||||
<div style="position:absolute;left:16px;top:50%;transform:translateY(-50%);font-size:32px">🛵</div>
|
||||
<div style="position:absolute;left:72px;top:50%;transform:translateY(-50%);color:var(--gd-danger);font-size:14px">反复喂信息…整理…又忘了…</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:16px">
|
||||
<div style="width:110px;text-align:right;font-weight:700;color:var(--gd-green)">有<br>知识库</div>
|
||||
<div style="flex:1;position:relative;height:70px;background:rgba(126,211,164,.06);border:1px solid rgba(126,211,164,.25);border-radius:16px">
|
||||
<div style="position:absolute;right:16px;top:50%;transform:translateY(-50%);font-size:32px">🏎️</div>
|
||||
<div style="position:absolute;right:72px;top:50%;transform:translateY(-50%);color:var(--gd-green);font-size:14px">AI 自己找 → 确认 → 干活!</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gd-grid-3" style="margin-top:36px">
|
||||
<div class="gd-glass gd-glass-warm"><div class="gd-big gd-grad">5×</div><p class="gd-dim" style="margin-top:6px">速度提升</p></div>
|
||||
<div class="gd-glass gd-glass-green"><div class="gd-big gd-grad">-80%</div><p class="gd-dim" style="margin-top:6px">重复喂信息</p></div>
|
||||
<div class="gd-glass gd-glass-blue"><div class="gd-big gd-grad">∞</div><p class="gd-dim" style="margin-top:6px">记忆持久化</p></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 6. PIPELINE -->
|
||||
<section class="slide">
|
||||
<div class="gd-ambient"><div class="gd-orb gd-orb-2"></div></div>
|
||||
<div class="gd-snum">06 / 08</div>
|
||||
<p class="gd-eyebrow">Pipeline</p>
|
||||
<h2 class="gd-h2">端到端 <span class="gd-grad">4 步走</span></h2>
|
||||
<div style="display:flex;align-items:center;justify-content:center;gap:8px;margin-top:36px">
|
||||
<div class="gd-glass" style="flex:1;text-align:center"><div style="font-size:34px">📂</div><div style="font-weight:600;margin-top:8px">Scan</div><div class="gd-dim" style="font-size:13px">递归读文件</div></div>
|
||||
<div style="color:var(--gd-text3);font-size:24px">→</div>
|
||||
<div class="gd-glass gd-glass-blue" style="flex:1;text-align:center"><div style="font-size:34px">🔬</div><div style="font-weight:600;margin-top:8px">Extract</div><div class="gd-dim" style="font-size:13px">LLM 抽实体</div></div>
|
||||
<div style="color:var(--gd-text3);font-size:24px">→</div>
|
||||
<div class="gd-glass gd-glass-green" style="flex:1;text-align:center"><div style="font-size:34px">🕸️</div><div style="font-weight:600;margin-top:8px">Build</div><div class="gd-dim" style="font-size:13px">构图 + 去重</div></div>
|
||||
<div style="color:var(--gd-text3);font-size:24px">→</div>
|
||||
<div class="gd-glass gd-glass-warm" style="flex:1;text-align:center"><div style="font-size:34px">🎨</div><div style="font-weight:600;margin-top:8px">Render</div><div class="gd-dim" style="font-size:13px">D3 交互图</div></div>
|
||||
</div>
|
||||
<div class="gd-glass" style="margin-top:32px"><p style="font-size:16px;line-height:1.6;color:var(--gd-text2)">每一步都有 audit log:你永远知道某个节点为什么存在、它来自哪个文件的哪一行。</p></div>
|
||||
</section>
|
||||
|
||||
<!-- 7. CTA -->
|
||||
<section class="slide">
|
||||
<div class="gd-ambient"><div class="gd-orb gd-orb-1"></div><div class="gd-orb gd-orb-3"></div></div>
|
||||
<div class="gd-snum">07 / 08</div>
|
||||
<p class="gd-eyebrow">Try it tonight</p>
|
||||
<h2 class="gd-h1" style="font-size:80px">Graphify <span class="gd-grad">your folders</span></h2>
|
||||
<p class="gd-cmd" style="margin-top:22px">$ npm i -g @lewis/graphify</p>
|
||||
<p class="gd-cmd" style="margin-top:10px;color:var(--gd-warm);text-shadow:0 0 30px rgba(232,168,124,.45)">$ graphify ~/obsidian-vault</p>
|
||||
<div style="margin-top:32px">
|
||||
<span class="gd-tag">#knowledge-graph</span>
|
||||
<span class="gd-tag">#open-source</span>
|
||||
<span class="gd-tag">#claude-agent</span>
|
||||
<span class="gd-tag">#obsidian</span>
|
||||
<span class="gd-tag">#d3-force</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 8. THANKS -->
|
||||
<section class="slide">
|
||||
<div class="gd-ambient"><div class="gd-orb gd-orb-2"></div></div>
|
||||
<div class="gd-snum">08 / 08</div>
|
||||
<div style="margin:auto 0;text-align:center">
|
||||
<div class="gd-big gd-rainbow" style="font-size:180px">Thanks.</div>
|
||||
<p class="gd-lede" style="margin:28px auto 0">github.com/lewis/graphify · 欢迎 star / issue / PR</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
78
skills/html-ppt-hermes-cyber-terminal/SKILL.md
Normal file
78
skills/html-ppt-hermes-cyber-terminal/SKILL.md
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
---
|
||||
name: html-ppt-hermes-cyber-terminal
|
||||
description: 暗终端 honest-review deck — #0a0c10 黑底 + 56px 赛博网格 + CRT 暗角 + 扫描线、窗口红绿灯 chrome、`$ prompt` 命令行标题、薄荷绿 #7ed3a4 大字、JetBrains Mono、stroke-only 柱状图、blinking 光标、琥珀/绿/红三档 tag、暗色代码块。适合 CLI / agent / dev tool 测评(含 trace、diff、benchmark)。
|
||||
triggers:
|
||||
- "terminal review"
|
||||
- "cli review"
|
||||
- "agent review"
|
||||
- "honest review"
|
||||
- "dev tool review"
|
||||
- "测评"
|
||||
od:
|
||||
mode: deck
|
||||
scenario: engineering
|
||||
featured: 30
|
||||
upstream: "https://github.com/lewislulu/html-ppt-skill"
|
||||
preview:
|
||||
type: html
|
||||
entry: index.html
|
||||
design_system:
|
||||
requires: false
|
||||
speaker_notes: true
|
||||
animations: true
|
||||
example_prompt: "用 html-ppt-hermes-cyber-terminal 模板做一份 CLI / agent 测评 PPT。深色终端风 + scanlines + 命令行标题 + benchmark 柱状图。先确认:被测评对象、3-5 个对比维度、benchmark 数据。"
|
||||
---
|
||||
# HTML PPT · 暗终端测评
|
||||
|
||||
A focused entry point into the [`html-ppt`](../html-ppt/SKILL.md) master skill that lands the user directly on the **`hermes-cyber-terminal`** full-deck template.
|
||||
|
||||
## When this card is picked
|
||||
|
||||
The Examples gallery wires "Use this prompt" to the example_prompt above. When you accept that prompt, this card is the right pick if the user wants exactly the visual identity of `hermes-cyber-terminal` (see the upstream [full-decks catalog](../html-ppt/references/full-decks.md) for screenshots and rationale).
|
||||
|
||||
## How to author the deck
|
||||
|
||||
1. **Read the master skill first.** All authoring rules live in
|
||||
[`skills/html-ppt/SKILL.md`](../html-ppt/SKILL.md) — content/audience checklist,
|
||||
token rules, layout reuse, presenter mode, the keyboard runtime, and the
|
||||
"never put presenter-only text on the slide" rule.
|
||||
2. **Start from the matching template folder:**
|
||||
`skills/html-ppt/templates/full-decks/hermes-cyber-terminal/` — copy `index.html` and
|
||||
`style.css` into the project, keep the `.tpl-hermes-cyber-terminal` body class.
|
||||
3. **Bring the shared runtime with the template.** The upstream
|
||||
`index.html` links the shared CSS/JS via `../../../assets/...` because it
|
||||
sits three folders deep inside `skills/html-ppt/templates/full-decks/`.
|
||||
Once you copy `index.html` into the project, those parent-relative URLs
|
||||
no longer resolve and `base.css`, `animations.css`, and `runtime.js`
|
||||
will 404 — meaning the deck never activates and slide navigation is
|
||||
dead. Pick one of these two recipes per project:
|
||||
- **Recipe A — copy + rewrite (preferred):** copy
|
||||
`skills/html-ppt/assets/fonts.css`, `skills/html-ppt/assets/base.css`,
|
||||
`skills/html-ppt/assets/animations/animations.css`, and
|
||||
`skills/html-ppt/assets/runtime.js` into a project-local
|
||||
`assets/` (with `assets/animations/animations.css`), then rewrite the
|
||||
four `<link>`/`<script>` tags in `index.html` from
|
||||
`../../../assets/...` to the matching project-local paths
|
||||
(`assets/fonts.css`, `assets/base.css`,
|
||||
`assets/animations/animations.css`, `assets/runtime.js`).
|
||||
- **Recipe B — inline:** read the same four files and replace each
|
||||
`<link rel="stylesheet" href="../../../assets/...">` with a
|
||||
`<style>...</style>` containing the file's contents, and the
|
||||
`<script src="../../../assets/runtime.js">` with a
|
||||
`<script>...</script>` containing `runtime.js`. Yields a single
|
||||
self-contained `index.html`.
|
||||
Either way, do not ship the upstream `../../../assets/...` URLs
|
||||
verbatim into a project artifact — they only work in-tree.
|
||||
4. **Pick a theme.** Default tokens look fine; if the user wants a different
|
||||
feel, swap in any of the 36 themes from `skills/html-ppt/assets/themes/*.css`
|
||||
via `<link id="theme-link">` and let `T` cycle.
|
||||
5. **Replace demo content, not classes.** The `.tpl-hermes-cyber-terminal` scoped CSS only
|
||||
recognises the structural classes shipped in the template — keep them.
|
||||
6. **Speaker notes go inside `<aside class="notes">` or `<div class="notes">`** — never as visible text on the slide.
|
||||
|
||||
## Attribution
|
||||
|
||||
Visual system, layouts, themes and the runtime keyboard model come from
|
||||
the upstream MIT-licensed [`lewislulu/html-ppt-skill`](https://github.com/lewislulu/html-ppt-skill). The
|
||||
LICENSE file ships at `skills/html-ppt/LICENSE`; please keep it in place when
|
||||
redistributing.
|
||||
422
skills/html-ppt-hermes-cyber-terminal/example.html
Normal file
422
skills/html-ppt-hermes-cyber-terminal/example.html
Normal file
|
|
@ -0,0 +1,422 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Hermes Cyber Terminal</title>
|
||||
<style>/* html-ppt :: shared webfonts */
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@200;300;400;500;600;700;800;900&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@200;300;400;500;600;700;900&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@300;400;600;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,600;0,800;1,400&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@300;400;500;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Archivo+Black&display=swap');
|
||||
|
||||
</style>
|
||||
<style>/* html-ppt :: base.css — reset + shared tokens + layout primitives */
|
||||
/* Default tokens. Themes in assets/themes/*.css override the :root block. */
|
||||
:root {
|
||||
--bg: #ffffff;
|
||||
--bg-soft: #f7f7f8;
|
||||
--surface: #ffffff;
|
||||
--surface-2: #f2f2f4;
|
||||
--border: rgba(0,0,0,.08);
|
||||
--border-strong: rgba(0,0,0,.16);
|
||||
--text-1: #111216;
|
||||
--text-2: #55596a;
|
||||
--text-3: #8a8f9e;
|
||||
--accent: #3b6cff;
|
||||
--accent-2: #7a5cff;
|
||||
--accent-3: #ff5c8a;
|
||||
--good: #1aaf6c;
|
||||
--warn: #f5a524;
|
||||
--bad: #e0445a;
|
||||
--grad: linear-gradient(135deg,#3b6cff,#7a5cff 55%,#ff5c8a);
|
||||
--grad-soft: linear-gradient(135deg,#eef2ff,#f5ecff 55%,#ffeef5);
|
||||
--radius: 18px;
|
||||
--radius-sm: 12px;
|
||||
--radius-lg: 26px;
|
||||
--shadow: 0 10px 30px rgba(18,24,40,.08), 0 2px 6px rgba(18,24,40,.04);
|
||||
--shadow-lg: 0 24px 60px rgba(18,24,40,.14), 0 6px 16px rgba(18,24,40,.06);
|
||||
--font-sans: 'Inter','Noto Sans SC',-apple-system,BlinkMacSystemFont,Helvetica,Arial,sans-serif;
|
||||
--font-serif: 'Playfair Display','Noto Serif SC',Georgia,serif;
|
||||
--font-mono: 'JetBrains Mono','IBM Plex Mono',SFMono-Regular,Menlo,monospace;
|
||||
--font-display: var(--font-sans);
|
||||
--letter-tight: -.03em;
|
||||
--letter-normal: -.01em;
|
||||
--ease: cubic-bezier(.4,0,.2,1);
|
||||
}
|
||||
|
||||
*,*::before,*::after{box-sizing:border-box}
|
||||
html,body{margin:0;padding:0;background:var(--bg);color:var(--text-1);
|
||||
font-family:var(--font-sans);font-weight:400;line-height:1.6;
|
||||
-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;
|
||||
letter-spacing:var(--letter-normal)}
|
||||
img,svg,video{max-width:100%;display:block}
|
||||
a{color:var(--accent);text-decoration:none}
|
||||
a:hover{text-decoration:underline}
|
||||
code,kbd,pre,samp{font-family:var(--font-mono)}
|
||||
|
||||
/* ================= SLIDE SYSTEM ================= */
|
||||
.deck{position:relative;width:100vw;height:100vh;overflow:hidden;background:var(--bg)}
|
||||
.slide{
|
||||
position:absolute;inset:0;
|
||||
display:flex;flex-direction:column;justify-content:center;
|
||||
padding:72px 96px;
|
||||
box-sizing:border-box;
|
||||
opacity:0;pointer-events:none;
|
||||
transition:opacity .5s var(--ease), transform .5s var(--ease);
|
||||
transform:translateX(30px);
|
||||
overflow:hidden;
|
||||
}
|
||||
.slide.is-active{opacity:1;pointer-events:auto;transform:translateX(0);z-index:2}
|
||||
.slide.is-prev{transform:translateX(-30px)}
|
||||
|
||||
/* single-page standalone (used when a layout file is opened directly) */
|
||||
body.single .slide{position:relative;width:100vw;height:100vh;opacity:1;transform:none;pointer-events:auto}
|
||||
|
||||
/* ================= TYPOGRAPHY ================= */
|
||||
.eyebrow{font-size:13px;font-weight:500;letter-spacing:.16em;text-transform:uppercase;color:var(--text-3)}
|
||||
.kicker{font-size:14px;font-weight:600;color:var(--accent);letter-spacing:.08em;text-transform:uppercase}
|
||||
h1.title,.h1{font-family:var(--font-display);font-size:72px;line-height:1.05;font-weight:800;letter-spacing:var(--letter-tight);margin:0 0 18px;color:var(--text-1)}
|
||||
h2.title,.h2{font-family:var(--font-display);font-size:54px;line-height:1.1;font-weight:700;letter-spacing:var(--letter-tight);margin:0 0 14px}
|
||||
h3,.h3{font-size:32px;line-height:1.2;font-weight:600;letter-spacing:var(--letter-normal);margin:0 0 10px}
|
||||
h4,.h4{font-size:22px;line-height:1.3;font-weight:600;margin:0 0 8px}
|
||||
.lede{font-size:22px;line-height:1.55;color:var(--text-2);font-weight:300;max-width:62ch}
|
||||
.dim{color:var(--text-2)}
|
||||
.dim2{color:var(--text-3)}
|
||||
.mono{font-family:var(--font-mono)}
|
||||
.serif{font-family:var(--font-serif)}
|
||||
.gradient-text{background:var(--grad);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;color:transparent}
|
||||
|
||||
/* ================= LAYOUT PRIMITIVES ================= */
|
||||
.stack>*+*{margin-top:14px}
|
||||
.row{display:flex;gap:24px;align-items:center}
|
||||
.row.wrap{flex-wrap:wrap}
|
||||
.grid{display:grid;gap:24px}
|
||||
.g2{grid-template-columns:repeat(2,1fr)}
|
||||
.g3{grid-template-columns:repeat(3,1fr)}
|
||||
.g4{grid-template-columns:repeat(4,1fr)}
|
||||
.center{display:flex;align-items:center;justify-content:center;text-align:center}
|
||||
.fill{flex:1}
|
||||
.sp-t{padding-top:24px}.sp-b{padding-bottom:24px}
|
||||
.mt-s{margin-top:8px}.mt-m{margin-top:18px}.mt-l{margin-top:32px}
|
||||
.mb-s{margin-bottom:8px}.mb-m{margin-bottom:18px}.mb-l{margin-bottom:32px}
|
||||
|
||||
/* ================= CARDS ================= */
|
||||
.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);
|
||||
padding:26px 28px;box-shadow:var(--shadow);position:relative;overflow:hidden}
|
||||
.card-soft{background:var(--surface-2);border:1px solid var(--border)}
|
||||
.card-outline{background:transparent;border:1.5px solid var(--border-strong);box-shadow:none}
|
||||
.card-accent{background:var(--surface);border-top:3px solid var(--accent)}
|
||||
.card-hover{transition:transform .3s var(--ease),box-shadow .3s var(--ease)}
|
||||
.card-hover:hover{transform:translateY(-4px);box-shadow:var(--shadow-lg)}
|
||||
|
||||
.pill{display:inline-block;padding:4px 12px;border-radius:999px;font-size:12px;font-weight:500;
|
||||
background:var(--surface-2);color:var(--text-2);border:1px solid var(--border)}
|
||||
.pill-accent{background:color-mix(in srgb,var(--accent) 12%,transparent);color:var(--accent);border-color:color-mix(in srgb,var(--accent) 28%,transparent)}
|
||||
|
||||
/* ================= BARS / DIVIDERS ================= */
|
||||
.divider{height:1px;background:var(--border);width:100%}
|
||||
.divider-accent{height:3px;width:72px;background:var(--accent);border-radius:2px}
|
||||
|
||||
/* ================= CHROME (header/footer/progress) ================= */
|
||||
.deck-header{position:absolute;top:24px;left:40px;right:40px;display:flex;align-items:center;justify-content:space-between;
|
||||
font-size:12px;color:var(--text-3);letter-spacing:.12em;text-transform:uppercase;z-index:10;pointer-events:none}
|
||||
.deck-footer{position:absolute;bottom:24px;left:40px;right:40px;display:flex;align-items:center;justify-content:space-between;
|
||||
font-size:12px;color:var(--text-3);z-index:10;pointer-events:none}
|
||||
.slide-number::before{content:attr(data-current)}
|
||||
.slide-number::after{content:" / " attr(data-total)}
|
||||
.progress-bar{position:fixed;left:0;right:0;bottom:0;height:3px;background:transparent;z-index:20}
|
||||
.progress-bar > span{display:block;height:100%;width:0;background:var(--accent);transition:width .3s var(--ease)}
|
||||
|
||||
/* ================= PRESENTER / OVERVIEW ================= */
|
||||
.notes{display:none!important}
|
||||
.notes-overlay{position:fixed;inset:auto 0 0 0;max-height:42vh;background:rgba(20,22,30,.95);color:#e8ebf4;
|
||||
padding:20px 32px;font-size:16px;line-height:1.6;border-top:1px solid rgba(255,255,255,.1);transform:translateY(100%);
|
||||
transition:transform .3s var(--ease);z-index:40;overflow:auto;font-family:var(--font-sans)}
|
||||
.notes-overlay.open{transform:translateY(0)}
|
||||
.overview{position:fixed;inset:0;background:rgba(10,12,18,.92);backdrop-filter:blur(12px);z-index:50;
|
||||
display:none;padding:40px;overflow:auto}
|
||||
.overview.open{display:grid;grid-template-columns:repeat(4,1fr);gap:20px;align-content:start}
|
||||
.overview .thumb{background:var(--surface);border:1px solid var(--border);border-radius:12px;
|
||||
aspect-ratio:16/9;overflow:hidden;cursor:pointer;position:relative;color:var(--text-1);padding:16px;
|
||||
font-size:11px;transition:transform .2s var(--ease)}
|
||||
.overview .thumb:hover{transform:scale(1.04)}
|
||||
.overview .thumb .n{position:absolute;top:8px;left:10px;font-weight:700;font-size:14px;color:var(--text-3)}
|
||||
.overview .thumb .t{position:absolute;bottom:10px;left:14px;right:14px;font-weight:600;color:var(--text-1)}
|
||||
|
||||
/* ================= PRESENTER VIEW ================= */
|
||||
/* Presenter view opens in a separate popup window (S key).
|
||||
* All presenter styles are self-contained in the popup HTML generated by runtime.js.
|
||||
* The audience window (this file) is NOT affected — it stays as normal deck view.
|
||||
* Only the .notes class below is needed to hide speaker notes from audience. */
|
||||
|
||||
/* ================= UTILITY ================= */
|
||||
.hidden{display:none!important}
|
||||
.nowrap{white-space:nowrap}
|
||||
.tr{text-align:right}.tc{text-align:center}.tl{text-align:left}
|
||||
.uppercase{text-transform:uppercase;letter-spacing:.12em}
|
||||
|
||||
/* ================= PRINT ================= */
|
||||
@media print{
|
||||
.slide{position:relative;opacity:1!important;transform:none!important;page-break-after:always;height:100vh}
|
||||
.deck-header,.deck-footer,.progress-bar,.notes-overlay,.overview{display:none!important}
|
||||
}
|
||||
|
||||
</style>
|
||||
<style>/* hermes-cyber-terminal — 暗终端 + 霓虹绿青 + 扫描线 */
|
||||
.tpl-hermes-cyber-terminal{
|
||||
--hc-bg:#0a0c10;
|
||||
--hc-bg2:#15151b;
|
||||
--hc-surface:#12141a;
|
||||
--hc-border:rgba(126,211,164,.18);
|
||||
--hc-ink:#e4e2d8;
|
||||
--hc-ink2:#8a8892;
|
||||
--hc-green:#7ed3a4;
|
||||
--hc-cyan:#64dfdf;
|
||||
--hc-amber:#e9c58a;
|
||||
--hc-rose:#d4a0b9;
|
||||
--hc-red:#ff6b6b;
|
||||
background:var(--hc-bg);
|
||||
color:var(--hc-ink);
|
||||
font-family:'JetBrains Mono','SF Mono','Inter','Noto Sans SC',monospace;
|
||||
}
|
||||
.tpl-hermes-cyber-terminal .slide{background:var(--hc-bg);color:var(--hc-ink);padding:60px 84px;overflow:hidden}
|
||||
.tpl-hermes-cyber-terminal .hc-scanlines{position:absolute;inset:0;pointer-events:none;z-index:3;background:repeating-linear-gradient(180deg,transparent 0,transparent 3px,rgba(126,211,164,.025) 3px,rgba(126,211,164,.025) 4px);mix-blend-mode:screen}
|
||||
.tpl-hermes-cyber-terminal .hc-grid{position:absolute;inset:0;pointer-events:none;opacity:.35;background-image:linear-gradient(rgba(126,211,164,.08) 1px,transparent 1px),linear-gradient(90deg,rgba(126,211,164,.08) 1px,transparent 1px);background-size:56px 56px;mask-image:radial-gradient(ellipse at 50% 50%,black 30%,transparent 80%)}
|
||||
.tpl-hermes-cyber-terminal .hc-vignette{position:absolute;inset:0;pointer-events:none;background:radial-gradient(ellipse at 50% 50%,transparent 50%,rgba(0,0,0,.6) 100%)}
|
||||
.tpl-hermes-cyber-terminal .slide > *{position:relative;z-index:2}
|
||||
.tpl-hermes-cyber-terminal .hc-chrome{display:flex;align-items:center;justify-content:space-between;margin-bottom:18px;font-size:11px;color:var(--hc-ink2);letter-spacing:.18em;text-transform:uppercase}
|
||||
.tpl-hermes-cyber-terminal .hc-chrome .dots{display:flex;gap:8px}
|
||||
.tpl-hermes-cyber-terminal .hc-chrome .dots span{width:11px;height:11px;border-radius:50%;background:#2a2d33}
|
||||
.tpl-hermes-cyber-terminal .hc-chrome .dots span:nth-child(1){background:#ff5f57}
|
||||
.tpl-hermes-cyber-terminal .hc-chrome .dots span:nth-child(2){background:#febc2e}
|
||||
.tpl-hermes-cyber-terminal .hc-chrome .dots span:nth-child(3){background:var(--hc-green)}
|
||||
.tpl-hermes-cyber-terminal .hc-prompt{color:var(--hc-green);font-weight:500}
|
||||
.tpl-hermes-cyber-terminal .hc-prompt::before{content:'$ ';color:var(--hc-cyan)}
|
||||
.tpl-hermes-cyber-terminal .hc-h1{font-family:'JetBrains Mono',monospace;font-size:72px;font-weight:700;line-height:1.05;letter-spacing:-.02em;color:var(--hc-green);text-shadow:0 0 30px rgba(126,211,164,.35),0 0 60px rgba(126,211,164,.1);margin:14px 0 12px}
|
||||
.tpl-hermes-cyber-terminal .hc-h2{font-size:46px;font-weight:600;color:var(--hc-ink);margin:0 0 10px;letter-spacing:-.015em}
|
||||
.tpl-hermes-cyber-terminal .hc-h3{font-size:22px;font-weight:600;color:var(--hc-amber);margin:0 0 10px}
|
||||
.tpl-hermes-cyber-terminal .hc-lede{font-size:18px;line-height:1.7;color:var(--hc-ink2);max-width:780px;font-family:'Inter','Noto Sans SC',sans-serif}
|
||||
.tpl-hermes-cyber-terminal .hc-cursor{display:inline-block;width:12px;height:1em;background:var(--hc-green);vertical-align:middle;margin-left:6px;animation:hcBlink 1s steps(2) infinite}
|
||||
@keyframes hcBlink{50%{opacity:0}}
|
||||
.tpl-hermes-cyber-terminal .hc-card{background:var(--hc-surface);border:1px solid var(--hc-border);border-radius:10px;padding:20px 24px;position:relative}
|
||||
.tpl-hermes-cyber-terminal .hc-card::before{content:'';position:absolute;top:-1px;left:12px;right:12px;height:2px;background:linear-gradient(90deg,transparent,var(--hc-green),transparent)}
|
||||
.tpl-hermes-cyber-terminal .hc-card .lbl{font-size:10px;letter-spacing:.22em;text-transform:uppercase;color:var(--hc-ink2);margin-bottom:8px}
|
||||
.tpl-hermes-cyber-terminal .hc-card .val{font-size:22px;font-weight:700;color:var(--hc-green);font-family:'JetBrains Mono',monospace}
|
||||
.tpl-hermes-cyber-terminal .hc-card .desc{font-size:13px;color:var(--hc-ink2);margin-top:10px;line-height:1.55;font-family:'Inter',sans-serif}
|
||||
.tpl-hermes-cyber-terminal .hc-grid-3{display:grid;grid-template-columns:1fr 1fr 1fr;gap:16px;margin-top:24px}
|
||||
.tpl-hermes-cyber-terminal .hc-grid-2{display:grid;grid-template-columns:1fr 1fr;gap:20px;margin-top:24px}
|
||||
.tpl-hermes-cyber-terminal .hc-codebox{background:#0c0d12;border:1px solid var(--hc-border);border-radius:10px;padding:22px 26px;font-family:'JetBrains Mono',monospace;font-size:14px;line-height:1.85;color:#d8d4c8;box-shadow:inset 0 0 60px rgba(126,211,164,.04)}
|
||||
.tpl-hermes-cyber-terminal .hc-codebox .cm{color:#5a6068}
|
||||
.tpl-hermes-cyber-terminal .hc-codebox .kw{color:var(--hc-amber)}
|
||||
.tpl-hermes-cyber-terminal .hc-codebox .st{color:var(--hc-green)}
|
||||
.tpl-hermes-cyber-terminal .hc-codebox .fn{color:var(--hc-cyan)}
|
||||
.tpl-hermes-cyber-terminal .hc-codebox .var{color:var(--hc-rose)}
|
||||
.tpl-hermes-cyber-terminal .hc-codebox .hl{color:#fff;background:rgba(126,211,164,.15);padding:0 4px;border-radius:3px}
|
||||
.tpl-hermes-cyber-terminal .hc-tag{display:inline-block;font-family:'JetBrains Mono',monospace;font-size:11px;padding:3px 10px;border:1px solid var(--hc-border);border-radius:4px;color:var(--hc-green);background:rgba(126,211,164,.04);margin:2px 6px 2px 0;text-transform:uppercase;letter-spacing:.1em}
|
||||
.tpl-hermes-cyber-terminal .hc-tag.amber{color:var(--hc-amber);border-color:rgba(233,197,138,.2);background:rgba(233,197,138,.04)}
|
||||
.tpl-hermes-cyber-terminal .hc-tag.red{color:var(--hc-red);border-color:rgba(255,107,107,.25);background:rgba(255,107,107,.05)}
|
||||
.tpl-hermes-cyber-terminal .hc-big{font-family:'JetBrains Mono',monospace;font-size:140px;font-weight:700;line-height:1;color:var(--hc-green);text-shadow:0 0 40px rgba(126,211,164,.4),0 0 80px rgba(126,211,164,.15);letter-spacing:-.04em}
|
||||
.tpl-hermes-cyber-terminal .hc-footer{position:absolute;left:84px;right:84px;bottom:32px;display:flex;justify-content:space-between;font-size:10px;color:var(--hc-ink2);letter-spacing:.2em;text-transform:uppercase;border-top:1px solid rgba(126,211,164,.1);padding-top:14px}
|
||||
|
||||
</style>
|
||||
<style>
|
||||
/* Static-preview fallback (runtime.js is absent — keep every slide visible) */
|
||||
.deck{height:auto;min-height:100vh;overflow:visible}
|
||||
.slide{position:relative;inset:auto;opacity:1;pointer-events:auto;transform:none;height:100vh;page-break-after:always}
|
||||
.deck-header,.deck-footer,.slide-number,.progress-bar,.notes-overlay,.overview{pointer-events:none}
|
||||
.notes{display:none!important}
|
||||
</style></head>
|
||||
<body class="tpl-hermes-cyber-terminal">
|
||||
<div class="deck">
|
||||
|
||||
<!-- 1. COVER -->
|
||||
<section class="slide is-active">
|
||||
<div class="hc-grid"></div>
|
||||
<div class="hc-vignette"></div>
|
||||
<div class="hc-scanlines"></div>
|
||||
<div class="hc-chrome"><div class="dots"><span></span><span></span><span></span></div><div>~/hermes · zsh · 118x42 · 01:37:04</div></div>
|
||||
<div style="margin:auto 0">
|
||||
<p class="hc-prompt">whoami --hermes</p>
|
||||
<h1 class="hc-h1">HERMES<br>AGENT / v0.9.2<span class="hc-cursor"></span></h1>
|
||||
<p class="hc-lede">一个号称能「自主跑完整软件工程任务」的命令行 agent。<br>真的好用?还是又一轮营销?—— 我连续跑了 72 小时,告诉你答案。</p>
|
||||
<div style="margin-top:26px">
|
||||
<span class="hc-tag">rust-core</span>
|
||||
<span class="hc-tag">mcp-native</span>
|
||||
<span class="hc-tag amber">72h-benchmark</span>
|
||||
<span class="hc-tag red">honest-review</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hc-footer"><span>hermes-review · lewis · 2026</span><span>01 / 08</span></div>
|
||||
</section>
|
||||
|
||||
<!-- 2. SECTION DIVIDER -->
|
||||
<section class="slide">
|
||||
<div class="hc-grid"></div>
|
||||
<div class="hc-scanlines"></div>
|
||||
<div class="hc-chrome"><div class="dots"><span></span><span></span><span></span></div><div>section · 01/04</div></div>
|
||||
<div style="margin:auto 0">
|
||||
<p class="hc-prompt">cat chapter_01.md</p>
|
||||
<h1 class="hc-h1" style="font-size:110px">// Setup</h1>
|
||||
<p class="hc-lede">从 <code style="color:var(--hc-amber)">brew install hermes</code> 到第一次 prompt —— 一共 4 分 22 秒。</p>
|
||||
</div>
|
||||
<div class="hc-footer"><span>section · setup</span><span>02 / 08</span></div>
|
||||
</section>
|
||||
|
||||
<!-- 3. CONTENT — spec cards -->
|
||||
<section class="slide">
|
||||
<div class="hc-grid"></div>
|
||||
<div class="hc-scanlines"></div>
|
||||
<div class="hc-chrome"><div class="dots"><span></span><span></span><span></span></div><div>benchmark · cold-start</div></div>
|
||||
<h2 class="hc-h2">开箱数据</h2>
|
||||
<p class="hc-lede">cold start → first-successful-task 三次平均</p>
|
||||
<div class="hc-grid-3">
|
||||
<div class="hc-card"><div class="lbl">install time</div><div class="val">42s</div><div class="desc">单 binary,无 docker,无 python env。</div></div>
|
||||
<div class="hc-card"><div class="lbl">first token</div><div class="val">1.8s</div><div class="desc">接入 claude-opus-4-6,无预热。</div></div>
|
||||
<div class="hc-card"><div class="lbl">first PR merged</div><div class="val">4m22s</div><div class="desc">跑的是 fix-a-typo 级别的低难度任务。</div></div>
|
||||
</div>
|
||||
<div class="hc-grid-2">
|
||||
<div class="hc-card"><div class="lbl">// verdict +</div><div class="val" style="color:var(--hc-green);font-size:18px">冷启动是真的快</div><div class="desc">和 OpenClaw 的 docker + pip 流程比,快不止一个数量级。</div></div>
|
||||
<div class="hc-card"><div class="lbl">// verdict -</div><div class="val" style="color:var(--hc-red);font-size:18px">MCP 服务器配置不够友好</div><div class="desc">env 变量需要手动塞进 ~/.hermes/env,文档几乎没写。</div></div>
|
||||
</div>
|
||||
<div class="hc-footer"><span>data · verified 3 runs</span><span>03 / 08</span></div>
|
||||
</section>
|
||||
|
||||
<!-- 4. CODE -->
|
||||
<section class="slide">
|
||||
<div class="hc-grid"></div>
|
||||
<div class="hc-scanlines"></div>
|
||||
<div class="hc-chrome"><div class="dots"><span></span><span></span><span></span></div><div>trace · hermes run</div></div>
|
||||
<p class="hc-prompt">hermes run "refactor auth module to use pkce"</p>
|
||||
<h3 class="hc-h3" style="margin-top:12px">↓ 真实 trace (节选)</h3>
|
||||
<pre class="hc-codebox" style="margin-top:10px"><span class="cm"># hermes v0.9.2 · session 42a1</span>
|
||||
[<span class="fn">plan</span>] <span class="st">"分析 src/auth/*.ts → 找 oauth flow → 抽成 pkce"</span>
|
||||
[<span class="fn">read</span>] src/auth/oauth.ts <span class="cm">// 214 lines</span>
|
||||
[<span class="fn">read</span>] src/auth/token.ts <span class="cm">// 88 lines</span>
|
||||
[<span class="kw">think</span>] <span class="st">"发现 implicit flow,改为 code+pkce,需新 state param"</span>
|
||||
[<span class="fn">edit</span>] src/auth/oauth.ts <span class="hl">+43 -17</span>
|
||||
[<span class="fn">edit</span>] src/auth/token.ts <span class="hl">+12 -4</span>
|
||||
[<span class="fn">test</span>] pnpm vitest auth <span class="st">PASS 18/18</span>
|
||||
[<span class="fn">commit</span>] <span class="var">"feat(auth): migrate to oauth2 code+pkce"</span>
|
||||
[<span class="fn">push</span>] origin feat/pkce-auth <span class="st">ok</span>
|
||||
|
||||
<span class="cm"># 总耗时 3m 14s · 14k tokens · $0.21</span></pre>
|
||||
<div class="hc-footer"><span>trace · live</span><span>04 / 08</span></div>
|
||||
</section>
|
||||
|
||||
<!-- 5. CHART -->
|
||||
<section class="slide">
|
||||
<div class="hc-grid"></div>
|
||||
<div class="hc-scanlines"></div>
|
||||
<div class="hc-chrome"><div class="dots"><span></span><span></span><span></span></div><div>benchmark · hermes vs openclaw</div></div>
|
||||
<h2 class="hc-h2">72 小时对比</h2>
|
||||
<p class="hc-lede">同一组 48 个 GitHub issue,两个 agent 各跑一遍</p>
|
||||
<svg viewBox="0 0 1000 380" style="width:100%;max-width:1040px;margin-top:24px" xmlns="http://www.w3.org/2000/svg">
|
||||
<g font-family="JetBrains Mono, monospace" font-size="13" fill="#8a8892">
|
||||
<!-- axis -->
|
||||
<line x1="80" y1="40" x2="80" y2="320" stroke="rgba(126,211,164,.2)"/>
|
||||
<line x1="80" y1="320" x2="960" y2="320" stroke="rgba(126,211,164,.2)"/>
|
||||
<!-- y labels -->
|
||||
<text x="70" y="46" text-anchor="end">100%</text>
|
||||
<text x="70" y="116" text-anchor="end">75%</text>
|
||||
<text x="70" y="186" text-anchor="end">50%</text>
|
||||
<text x="70" y="256" text-anchor="end">25%</text>
|
||||
<text x="70" y="324" text-anchor="end">0</text>
|
||||
<!-- bars: hermes -->
|
||||
<g>
|
||||
<rect x="130" y="80" width="80" height="240" fill="rgba(126,211,164,.15)" stroke="#7ed3a4" stroke-width="1.5"/>
|
||||
<text x="170" y="76" text-anchor="middle" fill="#7ed3a4" font-weight="700">82%</text>
|
||||
<text x="170" y="345" text-anchor="middle">resolved</text>
|
||||
<rect x="240" y="146" width="80" height="174" fill="rgba(126,211,164,.15)" stroke="#7ed3a4" stroke-width="1.5"/>
|
||||
<text x="280" y="142" text-anchor="middle" fill="#7ed3a4" font-weight="700">58%</text>
|
||||
<text x="280" y="345" text-anchor="middle">one-shot</text>
|
||||
<rect x="350" y="60" width="80" height="260" fill="rgba(126,211,164,.15)" stroke="#7ed3a4" stroke-width="1.5"/>
|
||||
<text x="390" y="56" text-anchor="middle" fill="#7ed3a4" font-weight="700">89%</text>
|
||||
<text x="390" y="345" text-anchor="middle">test-pass</text>
|
||||
<rect x="460" y="110" width="80" height="210" fill="rgba(126,211,164,.15)" stroke="#7ed3a4" stroke-width="1.5"/>
|
||||
<text x="500" y="106" text-anchor="middle" fill="#7ed3a4" font-weight="700">71%</text>
|
||||
<text x="500" y="345" text-anchor="middle">pr-merged</text>
|
||||
</g>
|
||||
<!-- bars: openclaw -->
|
||||
<g>
|
||||
<rect x="570" y="150" width="80" height="170" fill="rgba(233,197,138,.12)" stroke="#e9c58a" stroke-width="1.5"/>
|
||||
<text x="610" y="146" text-anchor="middle" fill="#e9c58a" font-weight="700">60%</text>
|
||||
<text x="610" y="345" text-anchor="middle">resolved</text>
|
||||
<rect x="680" y="212" width="80" height="108" fill="rgba(233,197,138,.12)" stroke="#e9c58a" stroke-width="1.5"/>
|
||||
<text x="720" y="208" text-anchor="middle" fill="#e9c58a" font-weight="700">38%</text>
|
||||
<text x="720" y="345" text-anchor="middle">one-shot</text>
|
||||
<rect x="790" y="130" width="80" height="190" fill="rgba(233,197,138,.12)" stroke="#e9c58a" stroke-width="1.5"/>
|
||||
<text x="830" y="126" text-anchor="middle" fill="#e9c58a" font-weight="700">67%</text>
|
||||
<text x="830" y="345" text-anchor="middle">test-pass</text>
|
||||
</g>
|
||||
<!-- legend -->
|
||||
<g transform="translate(820,50)">
|
||||
<rect x="0" y="0" width="14" height="14" fill="rgba(126,211,164,.15)" stroke="#7ed3a4"/>
|
||||
<text x="22" y="12" fill="#7ed3a4">hermes 0.9.2</text>
|
||||
<rect x="0" y="22" width="14" height="14" fill="rgba(233,197,138,.12)" stroke="#e9c58a"/>
|
||||
<text x="22" y="34" fill="#e9c58a">openclaw 2.1</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
<div class="hc-footer"><span>benchmark · n=48</span><span>05 / 08</span></div>
|
||||
</section>
|
||||
|
||||
<!-- 6. STATS -->
|
||||
<section class="slide">
|
||||
<div class="hc-grid"></div>
|
||||
<div class="hc-scanlines"></div>
|
||||
<div class="hc-chrome"><div class="dots"><span></span><span></span><span></span></div><div>tldr</div></div>
|
||||
<p class="hc-prompt">echo $VERDICT</p>
|
||||
<div class="hc-big">7.8<span style="font-size:60px;color:var(--hc-ink2)">/ 10</span></div>
|
||||
<p class="hc-lede" style="margin-top:14px">值得装,还不值得完全依赖。</p>
|
||||
<div class="hc-grid-2" style="margin-top:24px">
|
||||
<div class="hc-card"><div class="lbl">+ strong points</div><div class="desc">• rust 本体冷启快<br>• trace 可读性极强<br>• diff 审核友好,commit message 也写得合格</div></div>
|
||||
<div class="hc-card"><div class="lbl">- weak points</div><div class="desc">• plan 阶段偶尔跳步<br>• 超 50k LoC 仓库会 OOM<br>• MCP 配置需要手动塞 env</div></div>
|
||||
</div>
|
||||
<div class="hc-footer"><span>verdict · honest</span><span>06 / 08</span></div>
|
||||
</section>
|
||||
|
||||
<!-- 7. CTA -->
|
||||
<section class="slide">
|
||||
<div class="hc-grid"></div>
|
||||
<div class="hc-scanlines"></div>
|
||||
<div class="hc-chrome"><div class="dots"><span></span><span></span><span></span></div><div>install</div></div>
|
||||
<h2 class="hc-h2">想自己跑一遍?</h2>
|
||||
<p class="hc-lede">三条命令,不到 5 分钟就能看见它干第一件事。</p>
|
||||
<pre class="hc-codebox" style="margin-top:22px"><span class="cm"># 1. install</span>
|
||||
<span class="kw">$</span> brew install hermes-agent/tap/hermes
|
||||
|
||||
<span class="cm"># 2. auth (先准备好 anthropic api key)</span>
|
||||
<span class="kw">$</span> hermes auth login
|
||||
|
||||
<span class="cm"># 3. first task</span>
|
||||
<span class="kw">$</span> cd ~/your-repo && hermes run <span class="st">"add a CHANGELOG.md from git log"</span></pre>
|
||||
<div style="margin-top:26px">
|
||||
<span class="hc-tag">brew-ready</span>
|
||||
<span class="hc-tag">opus-4.6</span>
|
||||
<span class="hc-tag amber">needs-api-key</span>
|
||||
</div>
|
||||
<div class="hc-footer"><span>try-it-now</span><span>07 / 08</span></div>
|
||||
</section>
|
||||
|
||||
<!-- 8. THANKS -->
|
||||
<section class="slide">
|
||||
<div class="hc-grid"></div>
|
||||
<div class="hc-scanlines"></div>
|
||||
<div class="hc-chrome"><div class="dots"><span></span><span></span><span></span></div><div>EOF</div></div>
|
||||
<div style="margin:auto 0">
|
||||
<p class="hc-prompt">exit 0</p>
|
||||
<h1 class="hc-h1" style="font-size:120px">// thanks<span class="hc-cursor"></span></h1>
|
||||
<p class="hc-lede">完整 trace、48 个任务的 PR 列表、benchmark 脚本都在 <span style="color:var(--hc-amber)">github.com/lewis/hermes-review</span></p>
|
||||
</div>
|
||||
<div class="hc-footer"><span>session closed</span><span>08 / 08</span></div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
78
skills/html-ppt-knowledge-arch-blueprint/SKILL.md
Normal file
78
skills/html-ppt-knowledge-arch-blueprint/SKILL.md
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
---
|
||||
name: html-ppt-knowledge-arch-blueprint
|
||||
description: 奶油蓝图架构 deck — 奶油纸 #F0EAE0 底色 + 单一锈红 #B5392A 高亮、48px 蓝图网格 mask、2px 黑边硬卡片、pipeline 步骤盒(其中一个抬高)、右侧锈红 insight callout、Playfair 衬线大字、SVG 虚线反馈环。零渐变零软阴影,认真且印刷友好。
|
||||
triggers:
|
||||
- "architecture"
|
||||
- "blueprint"
|
||||
- "system design"
|
||||
- "架构图"
|
||||
- "data flow"
|
||||
- "engineering whitepaper"
|
||||
od:
|
||||
mode: deck
|
||||
scenario: engineering
|
||||
featured: 29
|
||||
upstream: "https://github.com/lewislulu/html-ppt-skill"
|
||||
preview:
|
||||
type: html
|
||||
entry: index.html
|
||||
design_system:
|
||||
requires: false
|
||||
speaker_notes: true
|
||||
animations: true
|
||||
example_prompt: "用 html-ppt-knowledge-arch-blueprint 模板做一份系统架构介绍 PPT。奶油纸底 + 锈红高亮 + 蓝图网格 + pipeline 抬高一格 + 衬线大字。先告诉我系统名 + 5-7 个核心模块 + 数据流方向,再写 8-10 页。"
|
||||
---
|
||||
# HTML PPT · 奶油蓝图架构
|
||||
|
||||
A focused entry point into the [`html-ppt`](../html-ppt/SKILL.md) master skill that lands the user directly on the **`knowledge-arch-blueprint`** full-deck template.
|
||||
|
||||
## When this card is picked
|
||||
|
||||
The Examples gallery wires "Use this prompt" to the example_prompt above. When you accept that prompt, this card is the right pick if the user wants exactly the visual identity of `knowledge-arch-blueprint` (see the upstream [full-decks catalog](../html-ppt/references/full-decks.md) for screenshots and rationale).
|
||||
|
||||
## How to author the deck
|
||||
|
||||
1. **Read the master skill first.** All authoring rules live in
|
||||
[`skills/html-ppt/SKILL.md`](../html-ppt/SKILL.md) — content/audience checklist,
|
||||
token rules, layout reuse, presenter mode, the keyboard runtime, and the
|
||||
"never put presenter-only text on the slide" rule.
|
||||
2. **Start from the matching template folder:**
|
||||
`skills/html-ppt/templates/full-decks/knowledge-arch-blueprint/` — copy `index.html` and
|
||||
`style.css` into the project, keep the `.tpl-knowledge-arch-blueprint` body class.
|
||||
3. **Bring the shared runtime with the template.** The upstream
|
||||
`index.html` links the shared CSS/JS via `../../../assets/...` because it
|
||||
sits three folders deep inside `skills/html-ppt/templates/full-decks/`.
|
||||
Once you copy `index.html` into the project, those parent-relative URLs
|
||||
no longer resolve and `base.css`, `animations.css`, and `runtime.js`
|
||||
will 404 — meaning the deck never activates and slide navigation is
|
||||
dead. Pick one of these two recipes per project:
|
||||
- **Recipe A — copy + rewrite (preferred):** copy
|
||||
`skills/html-ppt/assets/fonts.css`, `skills/html-ppt/assets/base.css`,
|
||||
`skills/html-ppt/assets/animations/animations.css`, and
|
||||
`skills/html-ppt/assets/runtime.js` into a project-local
|
||||
`assets/` (with `assets/animations/animations.css`), then rewrite the
|
||||
four `<link>`/`<script>` tags in `index.html` from
|
||||
`../../../assets/...` to the matching project-local paths
|
||||
(`assets/fonts.css`, `assets/base.css`,
|
||||
`assets/animations/animations.css`, `assets/runtime.js`).
|
||||
- **Recipe B — inline:** read the same four files and replace each
|
||||
`<link rel="stylesheet" href="../../../assets/...">` with a
|
||||
`<style>...</style>` containing the file's contents, and the
|
||||
`<script src="../../../assets/runtime.js">` with a
|
||||
`<script>...</script>` containing `runtime.js`. Yields a single
|
||||
self-contained `index.html`.
|
||||
Either way, do not ship the upstream `../../../assets/...` URLs
|
||||
verbatim into a project artifact — they only work in-tree.
|
||||
4. **Pick a theme.** Default tokens look fine; if the user wants a different
|
||||
feel, swap in any of the 36 themes from `skills/html-ppt/assets/themes/*.css`
|
||||
via `<link id="theme-link">` and let `T` cycle.
|
||||
5. **Replace demo content, not classes.** The `.tpl-knowledge-arch-blueprint` scoped CSS only
|
||||
recognises the structural classes shipped in the template — keep them.
|
||||
6. **Speaker notes go inside `<aside class="notes">` or `<div class="notes">`** — never as visible text on the slide.
|
||||
|
||||
## Attribution
|
||||
|
||||
Visual system, layouts, themes and the runtime keyboard model come from
|
||||
the upstream MIT-licensed [`lewislulu/html-ppt-skill`](https://github.com/lewislulu/html-ppt-skill). The
|
||||
LICENSE file ships at `skills/html-ppt/LICENSE`; please keep it in place when
|
||||
redistributing.
|
||||
407
skills/html-ppt-knowledge-arch-blueprint/example.html
Normal file
407
skills/html-ppt-knowledge-arch-blueprint/example.html
Normal file
|
|
@ -0,0 +1,407 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Knowledge Arch Blueprint</title>
|
||||
<style>/* html-ppt :: shared webfonts */
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@200;300;400;500;600;700;800;900&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@200;300;400;500;600;700;900&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@300;400;600;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,600;0,800;1,400&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@300;400;500;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Archivo+Black&display=swap');
|
||||
|
||||
</style>
|
||||
<style>/* html-ppt :: base.css — reset + shared tokens + layout primitives */
|
||||
/* Default tokens. Themes in assets/themes/*.css override the :root block. */
|
||||
:root {
|
||||
--bg: #ffffff;
|
||||
--bg-soft: #f7f7f8;
|
||||
--surface: #ffffff;
|
||||
--surface-2: #f2f2f4;
|
||||
--border: rgba(0,0,0,.08);
|
||||
--border-strong: rgba(0,0,0,.16);
|
||||
--text-1: #111216;
|
||||
--text-2: #55596a;
|
||||
--text-3: #8a8f9e;
|
||||
--accent: #3b6cff;
|
||||
--accent-2: #7a5cff;
|
||||
--accent-3: #ff5c8a;
|
||||
--good: #1aaf6c;
|
||||
--warn: #f5a524;
|
||||
--bad: #e0445a;
|
||||
--grad: linear-gradient(135deg,#3b6cff,#7a5cff 55%,#ff5c8a);
|
||||
--grad-soft: linear-gradient(135deg,#eef2ff,#f5ecff 55%,#ffeef5);
|
||||
--radius: 18px;
|
||||
--radius-sm: 12px;
|
||||
--radius-lg: 26px;
|
||||
--shadow: 0 10px 30px rgba(18,24,40,.08), 0 2px 6px rgba(18,24,40,.04);
|
||||
--shadow-lg: 0 24px 60px rgba(18,24,40,.14), 0 6px 16px rgba(18,24,40,.06);
|
||||
--font-sans: 'Inter','Noto Sans SC',-apple-system,BlinkMacSystemFont,Helvetica,Arial,sans-serif;
|
||||
--font-serif: 'Playfair Display','Noto Serif SC',Georgia,serif;
|
||||
--font-mono: 'JetBrains Mono','IBM Plex Mono',SFMono-Regular,Menlo,monospace;
|
||||
--font-display: var(--font-sans);
|
||||
--letter-tight: -.03em;
|
||||
--letter-normal: -.01em;
|
||||
--ease: cubic-bezier(.4,0,.2,1);
|
||||
}
|
||||
|
||||
*,*::before,*::after{box-sizing:border-box}
|
||||
html,body{margin:0;padding:0;background:var(--bg);color:var(--text-1);
|
||||
font-family:var(--font-sans);font-weight:400;line-height:1.6;
|
||||
-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;
|
||||
letter-spacing:var(--letter-normal)}
|
||||
img,svg,video{max-width:100%;display:block}
|
||||
a{color:var(--accent);text-decoration:none}
|
||||
a:hover{text-decoration:underline}
|
||||
code,kbd,pre,samp{font-family:var(--font-mono)}
|
||||
|
||||
/* ================= SLIDE SYSTEM ================= */
|
||||
.deck{position:relative;width:100vw;height:100vh;overflow:hidden;background:var(--bg)}
|
||||
.slide{
|
||||
position:absolute;inset:0;
|
||||
display:flex;flex-direction:column;justify-content:center;
|
||||
padding:72px 96px;
|
||||
box-sizing:border-box;
|
||||
opacity:0;pointer-events:none;
|
||||
transition:opacity .5s var(--ease), transform .5s var(--ease);
|
||||
transform:translateX(30px);
|
||||
overflow:hidden;
|
||||
}
|
||||
.slide.is-active{opacity:1;pointer-events:auto;transform:translateX(0);z-index:2}
|
||||
.slide.is-prev{transform:translateX(-30px)}
|
||||
|
||||
/* single-page standalone (used when a layout file is opened directly) */
|
||||
body.single .slide{position:relative;width:100vw;height:100vh;opacity:1;transform:none;pointer-events:auto}
|
||||
|
||||
/* ================= TYPOGRAPHY ================= */
|
||||
.eyebrow{font-size:13px;font-weight:500;letter-spacing:.16em;text-transform:uppercase;color:var(--text-3)}
|
||||
.kicker{font-size:14px;font-weight:600;color:var(--accent);letter-spacing:.08em;text-transform:uppercase}
|
||||
h1.title,.h1{font-family:var(--font-display);font-size:72px;line-height:1.05;font-weight:800;letter-spacing:var(--letter-tight);margin:0 0 18px;color:var(--text-1)}
|
||||
h2.title,.h2{font-family:var(--font-display);font-size:54px;line-height:1.1;font-weight:700;letter-spacing:var(--letter-tight);margin:0 0 14px}
|
||||
h3,.h3{font-size:32px;line-height:1.2;font-weight:600;letter-spacing:var(--letter-normal);margin:0 0 10px}
|
||||
h4,.h4{font-size:22px;line-height:1.3;font-weight:600;margin:0 0 8px}
|
||||
.lede{font-size:22px;line-height:1.55;color:var(--text-2);font-weight:300;max-width:62ch}
|
||||
.dim{color:var(--text-2)}
|
||||
.dim2{color:var(--text-3)}
|
||||
.mono{font-family:var(--font-mono)}
|
||||
.serif{font-family:var(--font-serif)}
|
||||
.gradient-text{background:var(--grad);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;color:transparent}
|
||||
|
||||
/* ================= LAYOUT PRIMITIVES ================= */
|
||||
.stack>*+*{margin-top:14px}
|
||||
.row{display:flex;gap:24px;align-items:center}
|
||||
.row.wrap{flex-wrap:wrap}
|
||||
.grid{display:grid;gap:24px}
|
||||
.g2{grid-template-columns:repeat(2,1fr)}
|
||||
.g3{grid-template-columns:repeat(3,1fr)}
|
||||
.g4{grid-template-columns:repeat(4,1fr)}
|
||||
.center{display:flex;align-items:center;justify-content:center;text-align:center}
|
||||
.fill{flex:1}
|
||||
.sp-t{padding-top:24px}.sp-b{padding-bottom:24px}
|
||||
.mt-s{margin-top:8px}.mt-m{margin-top:18px}.mt-l{margin-top:32px}
|
||||
.mb-s{margin-bottom:8px}.mb-m{margin-bottom:18px}.mb-l{margin-bottom:32px}
|
||||
|
||||
/* ================= CARDS ================= */
|
||||
.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);
|
||||
padding:26px 28px;box-shadow:var(--shadow);position:relative;overflow:hidden}
|
||||
.card-soft{background:var(--surface-2);border:1px solid var(--border)}
|
||||
.card-outline{background:transparent;border:1.5px solid var(--border-strong);box-shadow:none}
|
||||
.card-accent{background:var(--surface);border-top:3px solid var(--accent)}
|
||||
.card-hover{transition:transform .3s var(--ease),box-shadow .3s var(--ease)}
|
||||
.card-hover:hover{transform:translateY(-4px);box-shadow:var(--shadow-lg)}
|
||||
|
||||
.pill{display:inline-block;padding:4px 12px;border-radius:999px;font-size:12px;font-weight:500;
|
||||
background:var(--surface-2);color:var(--text-2);border:1px solid var(--border)}
|
||||
.pill-accent{background:color-mix(in srgb,var(--accent) 12%,transparent);color:var(--accent);border-color:color-mix(in srgb,var(--accent) 28%,transparent)}
|
||||
|
||||
/* ================= BARS / DIVIDERS ================= */
|
||||
.divider{height:1px;background:var(--border);width:100%}
|
||||
.divider-accent{height:3px;width:72px;background:var(--accent);border-radius:2px}
|
||||
|
||||
/* ================= CHROME (header/footer/progress) ================= */
|
||||
.deck-header{position:absolute;top:24px;left:40px;right:40px;display:flex;align-items:center;justify-content:space-between;
|
||||
font-size:12px;color:var(--text-3);letter-spacing:.12em;text-transform:uppercase;z-index:10;pointer-events:none}
|
||||
.deck-footer{position:absolute;bottom:24px;left:40px;right:40px;display:flex;align-items:center;justify-content:space-between;
|
||||
font-size:12px;color:var(--text-3);z-index:10;pointer-events:none}
|
||||
.slide-number::before{content:attr(data-current)}
|
||||
.slide-number::after{content:" / " attr(data-total)}
|
||||
.progress-bar{position:fixed;left:0;right:0;bottom:0;height:3px;background:transparent;z-index:20}
|
||||
.progress-bar > span{display:block;height:100%;width:0;background:var(--accent);transition:width .3s var(--ease)}
|
||||
|
||||
/* ================= PRESENTER / OVERVIEW ================= */
|
||||
.notes{display:none!important}
|
||||
.notes-overlay{position:fixed;inset:auto 0 0 0;max-height:42vh;background:rgba(20,22,30,.95);color:#e8ebf4;
|
||||
padding:20px 32px;font-size:16px;line-height:1.6;border-top:1px solid rgba(255,255,255,.1);transform:translateY(100%);
|
||||
transition:transform .3s var(--ease);z-index:40;overflow:auto;font-family:var(--font-sans)}
|
||||
.notes-overlay.open{transform:translateY(0)}
|
||||
.overview{position:fixed;inset:0;background:rgba(10,12,18,.92);backdrop-filter:blur(12px);z-index:50;
|
||||
display:none;padding:40px;overflow:auto}
|
||||
.overview.open{display:grid;grid-template-columns:repeat(4,1fr);gap:20px;align-content:start}
|
||||
.overview .thumb{background:var(--surface);border:1px solid var(--border);border-radius:12px;
|
||||
aspect-ratio:16/9;overflow:hidden;cursor:pointer;position:relative;color:var(--text-1);padding:16px;
|
||||
font-size:11px;transition:transform .2s var(--ease)}
|
||||
.overview .thumb:hover{transform:scale(1.04)}
|
||||
.overview .thumb .n{position:absolute;top:8px;left:10px;font-weight:700;font-size:14px;color:var(--text-3)}
|
||||
.overview .thumb .t{position:absolute;bottom:10px;left:14px;right:14px;font-weight:600;color:var(--text-1)}
|
||||
|
||||
/* ================= PRESENTER VIEW ================= */
|
||||
/* Presenter view opens in a separate popup window (S key).
|
||||
* All presenter styles are self-contained in the popup HTML generated by runtime.js.
|
||||
* The audience window (this file) is NOT affected — it stays as normal deck view.
|
||||
* Only the .notes class below is needed to hide speaker notes from audience. */
|
||||
|
||||
/* ================= UTILITY ================= */
|
||||
.hidden{display:none!important}
|
||||
.nowrap{white-space:nowrap}
|
||||
.tr{text-align:right}.tc{text-align:center}.tl{text-align:left}
|
||||
.uppercase{text-transform:uppercase;letter-spacing:.12em}
|
||||
|
||||
/* ================= PRINT ================= */
|
||||
@media print{
|
||||
.slide{position:relative;opacity:1!important;transform:none!important;page-break-after:always;height:100vh}
|
||||
.deck-header,.deck-footer,.progress-bar,.notes-overlay,.overview{display:none!important}
|
||||
}
|
||||
|
||||
</style>
|
||||
<style>/* knowledge-arch-blueprint — 奶油纸 + 建筑蓝图风 */
|
||||
.tpl-knowledge-arch-blueprint{
|
||||
--kb-bg:#F0EAE0;
|
||||
--kb-ink:#1a1a1a;
|
||||
--kb-ink2:#555;
|
||||
--kb-ink3:#aaa;
|
||||
--kb-rust:#B5392A;
|
||||
--kb-rust-soft:rgba(181,57,42,.08);
|
||||
--kb-line:#cec8be;
|
||||
background:var(--kb-bg);
|
||||
color:var(--kb-ink);
|
||||
font-family:'Inter','Noto Sans SC',-apple-system,sans-serif;
|
||||
}
|
||||
.tpl-knowledge-arch-blueprint .slide{background:var(--kb-bg);color:var(--kb-ink);padding:64px 80px}
|
||||
.tpl-knowledge-arch-blueprint .kb-grid-bg{position:absolute;inset:0;pointer-events:none;opacity:.35;background-image:linear-gradient(rgba(26,26,26,.06) 1px,transparent 1px),linear-gradient(90deg,rgba(26,26,26,.06) 1px,transparent 1px);background-size:48px 48px;mask-image:radial-gradient(ellipse at center,black 40%,transparent 85%)}
|
||||
.tpl-knowledge-arch-blueprint .slide > *{position:relative;z-index:2}
|
||||
.tpl-knowledge-arch-blueprint .kb-kicker{font-size:13px;font-weight:800;letter-spacing:4px;text-transform:uppercase;color:var(--kb-rust);margin-bottom:12px}
|
||||
.tpl-knowledge-arch-blueprint .kb-h1{font-size:66px;font-weight:900;line-height:1.08;color:#111;margin:0 0 14px;letter-spacing:-.02em}
|
||||
.tpl-knowledge-arch-blueprint .kb-h1 span.rust{color:var(--kb-rust)}
|
||||
.tpl-knowledge-arch-blueprint .kb-sub{font-size:20px;color:#666;line-height:1.55;max-width:780px}
|
||||
.tpl-knowledge-arch-blueprint .kb-insight{display:inline-block;background:var(--kb-rust);color:#fff;border-radius:10px;padding:16px 22px;font-size:14px;font-weight:700;line-height:1.5;max-width:340px;box-shadow:0 8px 24px rgba(181,57,42,.22)}
|
||||
.tpl-knowledge-arch-blueprint .kb-insight .kk{font-size:10px;letter-spacing:2px;opacity:.7;display:block;margin-bottom:6px;font-weight:800}
|
||||
.tpl-knowledge-arch-blueprint .kb-section-label{font-size:11px;font-weight:800;letter-spacing:3.5px;text-transform:uppercase;color:#aaa;margin:30px 0 12px;display:flex;align-items:center;gap:14px}
|
||||
.tpl-knowledge-arch-blueprint .kb-section-label::after{content:'';flex:1;height:1px;background:var(--kb-line)}
|
||||
.tpl-knowledge-arch-blueprint .kb-pipeline{display:flex;align-items:stretch;gap:14px;margin-top:24px}
|
||||
.tpl-knowledge-arch-blueprint .kb-step{flex:1;border:2px solid #1a1a1a;border-radius:12px;padding:22px 18px;background:#fff;position:relative;min-height:200px;display:flex;flex-direction:column}
|
||||
.tpl-knowledge-arch-blueprint .kb-step.hero{background:var(--kb-rust);border-color:var(--kb-rust);color:#fff;flex:1.25;box-shadow:0 10px 32px rgba(181,57,42,.28);transform:translateY(-10px)}
|
||||
.tpl-knowledge-arch-blueprint .kb-step-num{font-size:10px;font-weight:800;letter-spacing:2.5px;color:#bbb;margin-bottom:8px;text-transform:uppercase}
|
||||
.tpl-knowledge-arch-blueprint .kb-step.hero .kb-step-num{color:rgba(255,255,255,.6)}
|
||||
.tpl-knowledge-arch-blueprint .kb-step-title{font-size:22px;font-weight:900;line-height:1.15;color:#111;margin-bottom:8px}
|
||||
.tpl-knowledge-arch-blueprint .kb-step.hero .kb-step-title{color:#fff}
|
||||
.tpl-knowledge-arch-blueprint .kb-step-body{font-size:13px;line-height:1.55;color:#555;margin-top:auto}
|
||||
.tpl-knowledge-arch-blueprint .kb-step.hero .kb-step-body{color:rgba(255,255,255,.88)}
|
||||
.tpl-knowledge-arch-blueprint .kb-grid-2{display:grid;grid-template-columns:1fr 1fr;gap:20px;margin-top:24px}
|
||||
.tpl-knowledge-arch-blueprint .kb-card{background:#fff;border:2px solid #1a1a1a;border-radius:12px;padding:22px 24px}
|
||||
.tpl-knowledge-arch-blueprint .kb-card h4{font-size:20px;font-weight:900;margin-bottom:6px}
|
||||
.tpl-knowledge-arch-blueprint .kb-card p{font-size:14px;color:#555;line-height:1.55}
|
||||
.tpl-knowledge-arch-blueprint .kb-legend{display:flex;gap:18px;flex-wrap:wrap;margin-top:22px;font-size:12px;color:#666}
|
||||
.tpl-knowledge-arch-blueprint .kb-legend .d{display:flex;align-items:center;gap:8px}
|
||||
.tpl-knowledge-arch-blueprint .kb-legend .b{width:14px;height:14px;border:2px solid #1a1a1a;border-radius:3px}
|
||||
.tpl-knowledge-arch-blueprint .kb-legend .b.rust{background:var(--kb-rust);border-color:var(--kb-rust)}
|
||||
.tpl-knowledge-arch-blueprint .kb-footer{position:absolute;left:80px;right:80px;bottom:36px;display:flex;justify-content:space-between;font-size:11px;color:#999;letter-spacing:.15em;text-transform:uppercase;border-top:1px solid var(--kb-line);padding-top:16px}
|
||||
.tpl-knowledge-arch-blueprint .kb-mono{font-family:'JetBrains Mono',monospace}
|
||||
.tpl-knowledge-arch-blueprint .kb-codebox{background:#1a1a1a;color:#f0eae0;border-radius:12px;padding:22px 26px;font-family:'JetBrains Mono',monospace;font-size:14px;line-height:1.8;margin-top:20px;border:2px solid #1a1a1a}
|
||||
.tpl-knowledge-arch-blueprint .kb-codebox .cm{color:#7a766c}
|
||||
.tpl-knowledge-arch-blueprint .kb-codebox .kw{color:#e8a87c}
|
||||
.tpl-knowledge-arch-blueprint .kb-codebox .st{color:#b3d1bc}
|
||||
.tpl-knowledge-arch-blueprint .kb-codebox .hl{color:var(--kb-rust);background:rgba(255,255,255,.08);padding:0 4px;border-radius:3px}
|
||||
.tpl-knowledge-arch-blueprint .kb-big-num{font-family:'Playfair Display',Georgia,serif;font-size:200px;font-weight:900;line-height:.9;color:var(--kb-rust);letter-spacing:-.04em}
|
||||
|
||||
</style>
|
||||
<style>
|
||||
/* Static-preview fallback (runtime.js is absent — keep every slide visible) */
|
||||
.deck{height:auto;min-height:100vh;overflow:visible}
|
||||
.slide{position:relative;inset:auto;opacity:1;pointer-events:auto;transform:none;height:100vh;page-break-after:always}
|
||||
.deck-header,.deck-footer,.slide-number,.progress-bar,.notes-overlay,.overview{pointer-events:none}
|
||||
.notes{display:none!important}
|
||||
</style></head>
|
||||
<body class="tpl-knowledge-arch-blueprint">
|
||||
<div class="deck">
|
||||
|
||||
<!-- 1. COVER -->
|
||||
<section class="slide is-active">
|
||||
<div class="kb-grid-bg"></div>
|
||||
<div style="display:flex;justify-content:space-between;align-items:flex-start;gap:40px;margin-bottom:44px">
|
||||
<div>
|
||||
<div class="kb-kicker">Karpathy Stack · 架构图 v2</div>
|
||||
<h1 class="kb-h1">LLM <span class="rust">知识库</span> 的<br>工程化蓝图</h1>
|
||||
<p class="kb-sub">从「乱贴笔记」到「可审计、可纠错、可复用」的第二大脑 —— 这是我读完 Karpathy 的分享后画的一张系统图。</p>
|
||||
</div>
|
||||
<div class="kb-insight"><span class="kk">KEY INSIGHT</span>Karpathy 原版缺一块:<br>反馈闭环让错误能回流纠正。</div>
|
||||
</div>
|
||||
<div class="kb-section-label">Pipeline · End-to-end</div>
|
||||
<div class="kb-pipeline">
|
||||
<div class="kb-step"><div class="kb-step-num">STEP 01</div><div class="kb-step-title">采集</div><div class="kb-step-body">浏览器剪藏、PDF、Podcast 转写、聊天记录</div></div>
|
||||
<div class="kb-step"><div class="kb-step-num">STEP 02</div><div class="kb-step-title">去噪</div><div class="kb-step-body">清洗导航栏、广告、重复段落、低信噪素材</div></div>
|
||||
<div class="kb-step hero"><div class="kb-step-num">STEP 03 · CORE</div><div class="kb-step-title">Wiki 化</div><div class="kb-step-body">结构化成双链笔记,实体、关系、属性全在一起</div></div>
|
||||
<div class="kb-step"><div class="kb-step-num">STEP 04</div><div class="kb-step-title">使用</div><div class="kb-step-body">Agent 随时检索、回答、再写入</div></div>
|
||||
</div>
|
||||
<div class="kb-footer"><span>Blueprint · v2 · 2026.04</span><span>01 / 08</span></div>
|
||||
</section>
|
||||
|
||||
<!-- 2. SECTION DIVIDER -->
|
||||
<section class="slide">
|
||||
<div class="kb-grid-bg"></div>
|
||||
<div style="margin:auto 0">
|
||||
<div class="kb-kicker">Chapter One</div>
|
||||
<h1 class="kb-h1" style="font-size:120px">为什么 <span class="rust">笔记</span><br>不够用了</h1>
|
||||
<p class="kb-sub" style="font-size:24px;margin-top:20px">当你的知识量超过记忆容量,<br>你需要的不是更多文件,而是一张<b>可导航的图</b>。</p>
|
||||
</div>
|
||||
<div class="kb-footer"><span>Section · Chapter 1</span><span>02 / 08</span></div>
|
||||
</section>
|
||||
|
||||
<!-- 3. CONTENT 2-col -->
|
||||
<section class="slide">
|
||||
<div class="kb-grid-bg"></div>
|
||||
<div class="kb-kicker">Problem · Solution</div>
|
||||
<h1 class="kb-h1" style="font-size:48px">原版 vs <span class="rust">升级版</span></h1>
|
||||
<div class="kb-grid-2">
|
||||
<div class="kb-card">
|
||||
<div class="kb-kicker" style="color:#888">原版 Karpathy</div>
|
||||
<h4>一次性写入</h4>
|
||||
<p>采集 → 转写 → 存档,错了就错了。没有回路,没有修正机制,笔记越多越混乱。</p>
|
||||
</div>
|
||||
<div class="kb-card" style="background:var(--kb-rust-soft);border-color:var(--kb-rust)">
|
||||
<div class="kb-kicker">升级 v2</div>
|
||||
<h4>反馈闭环</h4>
|
||||
<p>AI 使用知识库时记录每次 miss / 幻觉 / 过期事实,自动回灌到源文件,让笔记会自我修正。</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="kb-legend">
|
||||
<div class="d"><span class="b"></span>普通节点</div>
|
||||
<div class="d"><span class="b rust"></span>核心节点 · 反馈回路入口</div>
|
||||
<div class="d">—— 数据流 ┈┈ 反馈回路</div>
|
||||
</div>
|
||||
<div class="kb-footer"><span>Content · Compare</span><span>03 / 08</span></div>
|
||||
</section>
|
||||
|
||||
<!-- 4. CODE -->
|
||||
<section class="slide">
|
||||
<div class="kb-grid-bg"></div>
|
||||
<div class="kb-kicker">Implementation · Skill Manifest</div>
|
||||
<h1 class="kb-h1" style="font-size:48px">反馈回路 <span class="rust">怎么实现</span></h1>
|
||||
<p class="kb-sub">一个 100 行的 Agent Skill,把「AI 用得顺不顺」回写成 vault 的一条条修订记录。</p>
|
||||
<pre class="kb-codebox"><span class="cm"># skills/wiki-feedback/SKILL.md</span>
|
||||
<span class="kw">name</span>: wiki-feedback
|
||||
<span class="kw">trigger</span>: <span class="st">"after every retrieval"</span>
|
||||
|
||||
<span class="kw">on_hit</span>: record(<span class="st">query, path, used=true</span>)
|
||||
<span class="kw">on_miss</span>: record(<span class="st">query, reason=</span><span class="hl">"not-in-vault"</span>)
|
||||
<span class="kw">on_wrong</span>: record(<span class="st">query, correction, path</span>)
|
||||
|
||||
<span class="kw">nightly</span>:
|
||||
- <span class="st">aggregate misses → suggest new notes</span>
|
||||
- <span class="st">aggregate wrongs → diff-patch old notes</span>
|
||||
- <span class="st">commit to git, open PR for human review</span></pre>
|
||||
<div class="kb-footer"><span>Content · Code</span><span>04 / 08</span></div>
|
||||
</section>
|
||||
|
||||
<!-- 5. DIAGRAM - SVG feedback loop -->
|
||||
<section class="slide">
|
||||
<div class="kb-grid-bg"></div>
|
||||
<div class="kb-kicker">System Diagram</div>
|
||||
<h1 class="kb-h1" style="font-size:44px">反馈回路全貌</h1>
|
||||
<svg viewBox="0 0 1200 520" style="width:100%;max-width:1200px;margin-top:20px" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto">
|
||||
<path d="M0,0 L10,5 L0,10 z" fill="#1a1a1a"/>
|
||||
</marker>
|
||||
<marker id="arrow-r" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto">
|
||||
<path d="M0,0 L10,5 L0,10 z" fill="#B5392A"/>
|
||||
</marker>
|
||||
</defs>
|
||||
<g font-family="Inter, sans-serif" font-size="16" font-weight="700">
|
||||
<!-- boxes -->
|
||||
<rect x="40" y="180" width="200" height="120" rx="10" fill="#fff" stroke="#1a1a1a" stroke-width="2"/>
|
||||
<text x="140" y="220" text-anchor="middle">Sources</text>
|
||||
<text x="140" y="250" text-anchor="middle" font-size="12" font-weight="400" fill="#666">web · pdf · chat</text>
|
||||
|
||||
<rect x="300" y="180" width="200" height="120" rx="10" fill="#fff" stroke="#1a1a1a" stroke-width="2"/>
|
||||
<text x="400" y="220" text-anchor="middle">Clean + Split</text>
|
||||
<text x="400" y="250" text-anchor="middle" font-size="12" font-weight="400" fill="#666">defuddle / chunker</text>
|
||||
|
||||
<rect x="560" y="160" width="220" height="160" rx="10" fill="#B5392A" stroke="#B5392A" stroke-width="2"/>
|
||||
<text x="670" y="210" text-anchor="middle" fill="#fff" font-size="20">Vault (Wiki)</text>
|
||||
<text x="670" y="240" text-anchor="middle" font-size="12" font-weight="400" fill="rgba(255,255,255,.8)">markdown · links</text>
|
||||
<text x="670" y="262" text-anchor="middle" font-size="12" font-weight="400" fill="rgba(255,255,255,.8)">bases · canvas</text>
|
||||
|
||||
<rect x="840" y="180" width="200" height="120" rx="10" fill="#fff" stroke="#1a1a1a" stroke-width="2"/>
|
||||
<text x="940" y="220" text-anchor="middle">Agent Use</text>
|
||||
<text x="940" y="250" text-anchor="middle" font-size="12" font-weight="400" fill="#666">retrieve / answer</text>
|
||||
|
||||
<!-- forward arrows -->
|
||||
<line x1="245" y1="240" x2="295" y2="240" stroke="#1a1a1a" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
<line x1="505" y1="240" x2="555" y2="240" stroke="#1a1a1a" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
<line x1="785" y1="240" x2="835" y2="240" stroke="#1a1a1a" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- feedback dashed -->
|
||||
<path d="M 940 180 Q 940 80, 670 80 Q 400 80, 400 180" fill="none" stroke="#B5392A" stroke-width="2" stroke-dasharray="6 6" marker-end="url(#arrow-r)"/>
|
||||
<rect x="580" y="58" width="180" height="30" rx="6" fill="#F0EAE0" stroke="#B5392A" stroke-width="1"/>
|
||||
<text x="670" y="78" text-anchor="middle" fill="#B5392A" font-size="12">FEEDBACK · wrong / miss</text>
|
||||
|
||||
<!-- bottom feedback to sources -->
|
||||
<path d="M 940 300 Q 940 420, 670 420 Q 140 420, 140 300" fill="none" stroke="#B5392A" stroke-width="2" stroke-dasharray="6 6" marker-end="url(#arrow-r)"/>
|
||||
<rect x="560" y="400" width="220" height="30" rx="6" fill="#F0EAE0" stroke="#B5392A" stroke-width="1"/>
|
||||
<text x="670" y="420" text-anchor="middle" fill="#B5392A" font-size="12">NIGHTLY · suggest new sources</text>
|
||||
</g>
|
||||
</svg>
|
||||
<div class="kb-footer"><span>Diagram · Feedback Loop</span><span>05 / 08</span></div>
|
||||
</section>
|
||||
|
||||
<!-- 6. STATS -->
|
||||
<section class="slide">
|
||||
<div class="kb-grid-bg"></div>
|
||||
<div class="kb-kicker">After 6 Months</div>
|
||||
<h1 class="kb-h1" style="font-size:44px">升级版 <span class="rust">跑了半年</span> 的数据</h1>
|
||||
<div style="display:grid;grid-template-columns:1.3fr 1fr 1fr;gap:24px;margin-top:28px;align-items:center">
|
||||
<div style="text-align:center"><div class="kb-big-num">13</div><p style="font-size:14px;color:#666;margin-top:6px;letter-spacing:.1em;text-transform:uppercase">关键优化项 · 全部落地</p></div>
|
||||
<div class="kb-card"><h4 style="color:var(--kb-rust)">-62%</h4><p>幻觉率(相比无反馈回路版本)</p></div>
|
||||
<div class="kb-card"><h4 style="color:var(--kb-rust)">+4.1×</h4><p>单次检索命中率</p></div>
|
||||
</div>
|
||||
<div class="kb-grid-2" style="margin-top:18px">
|
||||
<div class="kb-card"><h4>自动修订 227 条</h4><p>其中 189 条被人工批准合并,38 条被拒绝(数据已归档)。</p></div>
|
||||
<div class="kb-card"><h4>新增笔记 412 篇</h4><p>从 miss 日志聚类而来,每篇都有来源追溯。</p></div>
|
||||
</div>
|
||||
<div class="kb-footer"><span>Content · Stats</span><span>06 / 08</span></div>
|
||||
</section>
|
||||
|
||||
<!-- 7. CTA -->
|
||||
<section class="slide">
|
||||
<div class="kb-grid-bg"></div>
|
||||
<div class="kb-kicker">Next Step</div>
|
||||
<h1 class="kb-h1" style="font-size:60px">开始你的 <span class="rust">Wiki v2</span></h1>
|
||||
<p class="kb-sub">不用重写所有笔记。先接一条回路,让 AI 的每次使用都在「改好」你的 vault。</p>
|
||||
<div class="kb-pipeline">
|
||||
<div class="kb-step"><div class="kb-step-num">TONIGHT</div><div class="kb-step-title">装 Skill</div><div class="kb-step-body">pnpm i -g @lewis/wiki-feedback</div></div>
|
||||
<div class="kb-step"><div class="kb-step-num">DAY 2</div><div class="kb-step-title">跑 7 天</div><div class="kb-step-body">观察 miss log 自动累积</div></div>
|
||||
<div class="kb-step hero"><div class="kb-step-num">DAY 8 · CORE</div><div class="kb-step-title">第一次审 PR</div><div class="kb-step-body">花 15 分钟 review 自动生成的修订</div></div>
|
||||
<div class="kb-step"><div class="kb-step-num">MONTH 1</div><div class="kb-step-title">开始信它</div><div class="kb-step-body">你的 vault 会变成活的</div></div>
|
||||
</div>
|
||||
<div class="kb-footer"><span>CTA</span><span>07 / 08</span></div>
|
||||
</section>
|
||||
|
||||
<!-- 8. THANKS -->
|
||||
<section class="slide">
|
||||
<div class="kb-grid-bg"></div>
|
||||
<div style="margin:auto 0;text-align:center">
|
||||
<div class="kb-kicker">END · blueprint v2</div>
|
||||
<h1 class="kb-h1" style="font-size:140px;margin-top:24px">谢谢 <span class="rust">·</span> thanks</h1>
|
||||
<p class="kb-sub" style="margin:0 auto;font-size:22px">图纸、Skill、笔记模板都在 <b>github.com/lewis/karpathy-wiki-v2</b></p>
|
||||
</div>
|
||||
<div class="kb-footer"><span>End of deck</span><span>08 / 08</span></div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
78
skills/html-ppt-obsidian-claude-gradient/SKILL.md
Normal file
78
skills/html-ppt-obsidian-claude-gradient/SKILL.md
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
---
|
||||
name: html-ppt-obsidian-claude-gradient
|
||||
description: GitHub 暗紫渐变 deck — GitHub-dark #0d1117 + 紫蓝 radial 环境光 + 60px 网格 mask、居中布局、紫色 pill 标签、三色渐变标题(#a855f7→#60a5fa→#34d399)、GitHub 风代码 palette、紫色左边框高亮块。适合开发者工作流 / MCP / Agent / dev tool 教程,类似 GitHub Blog / Linear Changelog。
|
||||
triggers:
|
||||
- "github dark"
|
||||
- "developer tutorial"
|
||||
- "mcp tutorial"
|
||||
- "agent tutorial"
|
||||
- "dev workflow"
|
||||
- "changelog deck"
|
||||
od:
|
||||
mode: deck
|
||||
scenario: engineering
|
||||
featured: 31
|
||||
upstream: "https://github.com/lewislulu/html-ppt-skill"
|
||||
preview:
|
||||
type: html
|
||||
entry: index.html
|
||||
design_system:
|
||||
requires: false
|
||||
speaker_notes: true
|
||||
animations: true
|
||||
example_prompt: "用 html-ppt-obsidian-claude-gradient 模板做一份开发者教程 PPT。GitHub 暗紫渐变 + 居中布局 + 紫色 pill + 三色渐变标题 + 配置/步骤代码块。先确认:教什么、目标受众、要不要 MCP/Agent 配置示例。"
|
||||
---
|
||||
# HTML PPT · GitHub 暗紫渐变
|
||||
|
||||
A focused entry point into the [`html-ppt`](../html-ppt/SKILL.md) master skill that lands the user directly on the **`obsidian-claude-gradient`** full-deck template.
|
||||
|
||||
## When this card is picked
|
||||
|
||||
The Examples gallery wires "Use this prompt" to the example_prompt above. When you accept that prompt, this card is the right pick if the user wants exactly the visual identity of `obsidian-claude-gradient` (see the upstream [full-decks catalog](../html-ppt/references/full-decks.md) for screenshots and rationale).
|
||||
|
||||
## How to author the deck
|
||||
|
||||
1. **Read the master skill first.** All authoring rules live in
|
||||
[`skills/html-ppt/SKILL.md`](../html-ppt/SKILL.md) — content/audience checklist,
|
||||
token rules, layout reuse, presenter mode, the keyboard runtime, and the
|
||||
"never put presenter-only text on the slide" rule.
|
||||
2. **Start from the matching template folder:**
|
||||
`skills/html-ppt/templates/full-decks/obsidian-claude-gradient/` — copy `index.html` and
|
||||
`style.css` into the project, keep the `.tpl-obsidian-claude-gradient` body class.
|
||||
3. **Bring the shared runtime with the template.** The upstream
|
||||
`index.html` links the shared CSS/JS via `../../../assets/...` because it
|
||||
sits three folders deep inside `skills/html-ppt/templates/full-decks/`.
|
||||
Once you copy `index.html` into the project, those parent-relative URLs
|
||||
no longer resolve and `base.css`, `animations.css`, and `runtime.js`
|
||||
will 404 — meaning the deck never activates and slide navigation is
|
||||
dead. Pick one of these two recipes per project:
|
||||
- **Recipe A — copy + rewrite (preferred):** copy
|
||||
`skills/html-ppt/assets/fonts.css`, `skills/html-ppt/assets/base.css`,
|
||||
`skills/html-ppt/assets/animations/animations.css`, and
|
||||
`skills/html-ppt/assets/runtime.js` into a project-local
|
||||
`assets/` (with `assets/animations/animations.css`), then rewrite the
|
||||
four `<link>`/`<script>` tags in `index.html` from
|
||||
`../../../assets/...` to the matching project-local paths
|
||||
(`assets/fonts.css`, `assets/base.css`,
|
||||
`assets/animations/animations.css`, `assets/runtime.js`).
|
||||
- **Recipe B — inline:** read the same four files and replace each
|
||||
`<link rel="stylesheet" href="../../../assets/...">` with a
|
||||
`<style>...</style>` containing the file's contents, and the
|
||||
`<script src="../../../assets/runtime.js">` with a
|
||||
`<script>...</script>` containing `runtime.js`. Yields a single
|
||||
self-contained `index.html`.
|
||||
Either way, do not ship the upstream `../../../assets/...` URLs
|
||||
verbatim into a project artifact — they only work in-tree.
|
||||
4. **Pick a theme.** Default tokens look fine; if the user wants a different
|
||||
feel, swap in any of the 36 themes from `skills/html-ppt/assets/themes/*.css`
|
||||
via `<link id="theme-link">` and let `T` cycle.
|
||||
5. **Replace demo content, not classes.** The `.tpl-obsidian-claude-gradient` scoped CSS only
|
||||
recognises the structural classes shipped in the template — keep them.
|
||||
6. **Speaker notes go inside `<aside class="notes">` or `<div class="notes">`** — never as visible text on the slide.
|
||||
|
||||
## Attribution
|
||||
|
||||
Visual system, layouts, themes and the runtime keyboard model come from
|
||||
the upstream MIT-licensed [`lewislulu/html-ppt-skill`](https://github.com/lewislulu/html-ppt-skill). The
|
||||
LICENSE file ships at `skills/html-ppt/LICENSE`; please keep it in place when
|
||||
redistributing.
|
||||
371
skills/html-ppt-obsidian-claude-gradient/example.html
Normal file
371
skills/html-ppt-obsidian-claude-gradient/example.html
Normal file
|
|
@ -0,0 +1,371 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Obsidian × Claude Gradient</title>
|
||||
<style>/* html-ppt :: shared webfonts */
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@200;300;400;500;600;700;800;900&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@200;300;400;500;600;700;900&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@300;400;600;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,600;0,800;1,400&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@300;400;500;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Archivo+Black&display=swap');
|
||||
|
||||
</style>
|
||||
<style>/* html-ppt :: base.css — reset + shared tokens + layout primitives */
|
||||
/* Default tokens. Themes in assets/themes/*.css override the :root block. */
|
||||
:root {
|
||||
--bg: #ffffff;
|
||||
--bg-soft: #f7f7f8;
|
||||
--surface: #ffffff;
|
||||
--surface-2: #f2f2f4;
|
||||
--border: rgba(0,0,0,.08);
|
||||
--border-strong: rgba(0,0,0,.16);
|
||||
--text-1: #111216;
|
||||
--text-2: #55596a;
|
||||
--text-3: #8a8f9e;
|
||||
--accent: #3b6cff;
|
||||
--accent-2: #7a5cff;
|
||||
--accent-3: #ff5c8a;
|
||||
--good: #1aaf6c;
|
||||
--warn: #f5a524;
|
||||
--bad: #e0445a;
|
||||
--grad: linear-gradient(135deg,#3b6cff,#7a5cff 55%,#ff5c8a);
|
||||
--grad-soft: linear-gradient(135deg,#eef2ff,#f5ecff 55%,#ffeef5);
|
||||
--radius: 18px;
|
||||
--radius-sm: 12px;
|
||||
--radius-lg: 26px;
|
||||
--shadow: 0 10px 30px rgba(18,24,40,.08), 0 2px 6px rgba(18,24,40,.04);
|
||||
--shadow-lg: 0 24px 60px rgba(18,24,40,.14), 0 6px 16px rgba(18,24,40,.06);
|
||||
--font-sans: 'Inter','Noto Sans SC',-apple-system,BlinkMacSystemFont,Helvetica,Arial,sans-serif;
|
||||
--font-serif: 'Playfair Display','Noto Serif SC',Georgia,serif;
|
||||
--font-mono: 'JetBrains Mono','IBM Plex Mono',SFMono-Regular,Menlo,monospace;
|
||||
--font-display: var(--font-sans);
|
||||
--letter-tight: -.03em;
|
||||
--letter-normal: -.01em;
|
||||
--ease: cubic-bezier(.4,0,.2,1);
|
||||
}
|
||||
|
||||
*,*::before,*::after{box-sizing:border-box}
|
||||
html,body{margin:0;padding:0;background:var(--bg);color:var(--text-1);
|
||||
font-family:var(--font-sans);font-weight:400;line-height:1.6;
|
||||
-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;
|
||||
letter-spacing:var(--letter-normal)}
|
||||
img,svg,video{max-width:100%;display:block}
|
||||
a{color:var(--accent);text-decoration:none}
|
||||
a:hover{text-decoration:underline}
|
||||
code,kbd,pre,samp{font-family:var(--font-mono)}
|
||||
|
||||
/* ================= SLIDE SYSTEM ================= */
|
||||
.deck{position:relative;width:100vw;height:100vh;overflow:hidden;background:var(--bg)}
|
||||
.slide{
|
||||
position:absolute;inset:0;
|
||||
display:flex;flex-direction:column;justify-content:center;
|
||||
padding:72px 96px;
|
||||
box-sizing:border-box;
|
||||
opacity:0;pointer-events:none;
|
||||
transition:opacity .5s var(--ease), transform .5s var(--ease);
|
||||
transform:translateX(30px);
|
||||
overflow:hidden;
|
||||
}
|
||||
.slide.is-active{opacity:1;pointer-events:auto;transform:translateX(0);z-index:2}
|
||||
.slide.is-prev{transform:translateX(-30px)}
|
||||
|
||||
/* single-page standalone (used when a layout file is opened directly) */
|
||||
body.single .slide{position:relative;width:100vw;height:100vh;opacity:1;transform:none;pointer-events:auto}
|
||||
|
||||
/* ================= TYPOGRAPHY ================= */
|
||||
.eyebrow{font-size:13px;font-weight:500;letter-spacing:.16em;text-transform:uppercase;color:var(--text-3)}
|
||||
.kicker{font-size:14px;font-weight:600;color:var(--accent);letter-spacing:.08em;text-transform:uppercase}
|
||||
h1.title,.h1{font-family:var(--font-display);font-size:72px;line-height:1.05;font-weight:800;letter-spacing:var(--letter-tight);margin:0 0 18px;color:var(--text-1)}
|
||||
h2.title,.h2{font-family:var(--font-display);font-size:54px;line-height:1.1;font-weight:700;letter-spacing:var(--letter-tight);margin:0 0 14px}
|
||||
h3,.h3{font-size:32px;line-height:1.2;font-weight:600;letter-spacing:var(--letter-normal);margin:0 0 10px}
|
||||
h4,.h4{font-size:22px;line-height:1.3;font-weight:600;margin:0 0 8px}
|
||||
.lede{font-size:22px;line-height:1.55;color:var(--text-2);font-weight:300;max-width:62ch}
|
||||
.dim{color:var(--text-2)}
|
||||
.dim2{color:var(--text-3)}
|
||||
.mono{font-family:var(--font-mono)}
|
||||
.serif{font-family:var(--font-serif)}
|
||||
.gradient-text{background:var(--grad);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;color:transparent}
|
||||
|
||||
/* ================= LAYOUT PRIMITIVES ================= */
|
||||
.stack>*+*{margin-top:14px}
|
||||
.row{display:flex;gap:24px;align-items:center}
|
||||
.row.wrap{flex-wrap:wrap}
|
||||
.grid{display:grid;gap:24px}
|
||||
.g2{grid-template-columns:repeat(2,1fr)}
|
||||
.g3{grid-template-columns:repeat(3,1fr)}
|
||||
.g4{grid-template-columns:repeat(4,1fr)}
|
||||
.center{display:flex;align-items:center;justify-content:center;text-align:center}
|
||||
.fill{flex:1}
|
||||
.sp-t{padding-top:24px}.sp-b{padding-bottom:24px}
|
||||
.mt-s{margin-top:8px}.mt-m{margin-top:18px}.mt-l{margin-top:32px}
|
||||
.mb-s{margin-bottom:8px}.mb-m{margin-bottom:18px}.mb-l{margin-bottom:32px}
|
||||
|
||||
/* ================= CARDS ================= */
|
||||
.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);
|
||||
padding:26px 28px;box-shadow:var(--shadow);position:relative;overflow:hidden}
|
||||
.card-soft{background:var(--surface-2);border:1px solid var(--border)}
|
||||
.card-outline{background:transparent;border:1.5px solid var(--border-strong);box-shadow:none}
|
||||
.card-accent{background:var(--surface);border-top:3px solid var(--accent)}
|
||||
.card-hover{transition:transform .3s var(--ease),box-shadow .3s var(--ease)}
|
||||
.card-hover:hover{transform:translateY(-4px);box-shadow:var(--shadow-lg)}
|
||||
|
||||
.pill{display:inline-block;padding:4px 12px;border-radius:999px;font-size:12px;font-weight:500;
|
||||
background:var(--surface-2);color:var(--text-2);border:1px solid var(--border)}
|
||||
.pill-accent{background:color-mix(in srgb,var(--accent) 12%,transparent);color:var(--accent);border-color:color-mix(in srgb,var(--accent) 28%,transparent)}
|
||||
|
||||
/* ================= BARS / DIVIDERS ================= */
|
||||
.divider{height:1px;background:var(--border);width:100%}
|
||||
.divider-accent{height:3px;width:72px;background:var(--accent);border-radius:2px}
|
||||
|
||||
/* ================= CHROME (header/footer/progress) ================= */
|
||||
.deck-header{position:absolute;top:24px;left:40px;right:40px;display:flex;align-items:center;justify-content:space-between;
|
||||
font-size:12px;color:var(--text-3);letter-spacing:.12em;text-transform:uppercase;z-index:10;pointer-events:none}
|
||||
.deck-footer{position:absolute;bottom:24px;left:40px;right:40px;display:flex;align-items:center;justify-content:space-between;
|
||||
font-size:12px;color:var(--text-3);z-index:10;pointer-events:none}
|
||||
.slide-number::before{content:attr(data-current)}
|
||||
.slide-number::after{content:" / " attr(data-total)}
|
||||
.progress-bar{position:fixed;left:0;right:0;bottom:0;height:3px;background:transparent;z-index:20}
|
||||
.progress-bar > span{display:block;height:100%;width:0;background:var(--accent);transition:width .3s var(--ease)}
|
||||
|
||||
/* ================= PRESENTER / OVERVIEW ================= */
|
||||
.notes{display:none!important}
|
||||
.notes-overlay{position:fixed;inset:auto 0 0 0;max-height:42vh;background:rgba(20,22,30,.95);color:#e8ebf4;
|
||||
padding:20px 32px;font-size:16px;line-height:1.6;border-top:1px solid rgba(255,255,255,.1);transform:translateY(100%);
|
||||
transition:transform .3s var(--ease);z-index:40;overflow:auto;font-family:var(--font-sans)}
|
||||
.notes-overlay.open{transform:translateY(0)}
|
||||
.overview{position:fixed;inset:0;background:rgba(10,12,18,.92);backdrop-filter:blur(12px);z-index:50;
|
||||
display:none;padding:40px;overflow:auto}
|
||||
.overview.open{display:grid;grid-template-columns:repeat(4,1fr);gap:20px;align-content:start}
|
||||
.overview .thumb{background:var(--surface);border:1px solid var(--border);border-radius:12px;
|
||||
aspect-ratio:16/9;overflow:hidden;cursor:pointer;position:relative;color:var(--text-1);padding:16px;
|
||||
font-size:11px;transition:transform .2s var(--ease)}
|
||||
.overview .thumb:hover{transform:scale(1.04)}
|
||||
.overview .thumb .n{position:absolute;top:8px;left:10px;font-weight:700;font-size:14px;color:var(--text-3)}
|
||||
.overview .thumb .t{position:absolute;bottom:10px;left:14px;right:14px;font-weight:600;color:var(--text-1)}
|
||||
|
||||
/* ================= PRESENTER VIEW ================= */
|
||||
/* Presenter view opens in a separate popup window (S key).
|
||||
* All presenter styles are self-contained in the popup HTML generated by runtime.js.
|
||||
* The audience window (this file) is NOT affected — it stays as normal deck view.
|
||||
* Only the .notes class below is needed to hide speaker notes from audience. */
|
||||
|
||||
/* ================= UTILITY ================= */
|
||||
.hidden{display:none!important}
|
||||
.nowrap{white-space:nowrap}
|
||||
.tr{text-align:right}.tc{text-align:center}.tl{text-align:left}
|
||||
.uppercase{text-transform:uppercase;letter-spacing:.12em}
|
||||
|
||||
/* ================= PRINT ================= */
|
||||
@media print{
|
||||
.slide{position:relative;opacity:1!important;transform:none!important;page-break-after:always;height:100vh}
|
||||
.deck-header,.deck-footer,.progress-bar,.notes-overlay,.overview{display:none!important}
|
||||
}
|
||||
|
||||
</style>
|
||||
<style>/* obsidian-claude-gradient — 紫色暗底 + GitHub-ish 渐变卡 */
|
||||
.tpl-obsidian-claude-gradient{
|
||||
--oc-bg:#0d1117;
|
||||
--oc-surface:#161b22;
|
||||
--oc-surface2:#21262d;
|
||||
--oc-border:#30363d;
|
||||
--oc-accent:#7c3aed;
|
||||
--oc-accent2:#a855f7;
|
||||
--oc-accent3:#c084fc;
|
||||
--oc-green:#3fb950;
|
||||
--oc-blue:#58a6ff;
|
||||
--oc-orange:#f97316;
|
||||
--oc-yellow:#fbbf24;
|
||||
--oc-red:#f87171;
|
||||
--oc-text:#e6edf3;
|
||||
--oc-dim:#8b949e;
|
||||
--oc-dimmer:#484f58;
|
||||
background:var(--oc-bg);
|
||||
color:var(--oc-text);
|
||||
font-family:'Inter','Noto Sans SC','PingFang SC',-apple-system,sans-serif;
|
||||
}
|
||||
.tpl-obsidian-claude-gradient .slide{background:var(--oc-bg);color:var(--oc-text);padding:64px 88px;display:flex;flex-direction:column;justify-content:center;align-items:center;text-align:center;overflow:hidden}
|
||||
.tpl-obsidian-claude-gradient .oc-cbg{position:absolute;inset:0;pointer-events:none;background:radial-gradient(ellipse at 28% 38%,rgba(124,58,237,.25) 0%,transparent 60%),radial-gradient(ellipse at 72% 62%,rgba(88,166,255,.18) 0%,transparent 60%)}
|
||||
.tpl-obsidian-claude-gradient .oc-cgrid{position:absolute;inset:0;pointer-events:none;background-image:linear-gradient(rgba(48,54,61,.4) 1px,transparent 1px),linear-gradient(90deg,rgba(48,54,61,.4) 1px,transparent 1px);background-size:60px 60px;mask-image:radial-gradient(ellipse at center,black 35%,transparent 80%)}
|
||||
.tpl-obsidian-claude-gradient .slide > *{position:relative;z-index:2}
|
||||
.tpl-obsidian-claude-gradient .oc-snum{position:absolute;top:24px;right:36px;color:var(--oc-dimmer);font-size:12px;letter-spacing:.1em;z-index:3}
|
||||
.tpl-obsidian-claude-gradient .oc-tag{display:inline-flex;align-items:center;gap:6px;font-size:11px;font-weight:700;letter-spacing:.12em;text-transform:uppercase;color:var(--oc-accent3);background:rgba(124,58,237,.14);border:1px solid rgba(168,85,247,.3);padding:5px 16px;border-radius:999px;margin-bottom:22px}
|
||||
.tpl-obsidian-claude-gradient .oc-h1{font-size:72px;font-weight:800;line-height:1.08;letter-spacing:-.02em;margin:0 0 10px;color:var(--oc-text)}
|
||||
.tpl-obsidian-claude-gradient .oc-h2{font-size:44px;font-weight:700;line-height:1.18;letter-spacing:-.015em;margin:0 0 14px}
|
||||
.tpl-obsidian-claude-gradient .oc-sub{font-size:19px;color:var(--oc-dim);line-height:1.65;max-width:720px;margin-top:14px}
|
||||
.tpl-obsidian-claude-gradient .oc-g{background:linear-gradient(135deg,#a855f7 0%,#60a5fa 55%,#34d399 100%);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}
|
||||
.tpl-obsidian-claude-gradient .oc-card{background:var(--oc-surface);border:1px solid var(--oc-border);border-radius:14px;padding:22px 26px;text-align:left;position:relative;overflow:hidden}
|
||||
.tpl-obsidian-claude-gradient .oc-card::before{content:'';position:absolute;top:0;left:0;right:0;height:1px;background:linear-gradient(90deg,transparent,rgba(168,85,247,.4),transparent)}
|
||||
.tpl-obsidian-claude-gradient .oc-grid-2{display:grid;grid-template-columns:1fr 1fr;gap:18px;width:100%;max-width:1000px;margin-top:24px}
|
||||
.tpl-obsidian-claude-gradient .oc-grid-3{display:grid;grid-template-columns:1fr 1fr 1fr;gap:16px;width:100%;max-width:1080px;margin-top:24px}
|
||||
.tpl-obsidian-claude-gradient .oc-badge{display:inline-flex;align-items:center;gap:5px;font-size:11px;font-weight:600;padding:3px 11px;border-radius:999px;margin-bottom:10px}
|
||||
.tpl-obsidian-claude-gradient .oc-bp{background:rgba(168,85,247,.15);color:var(--oc-accent3)}
|
||||
.tpl-obsidian-claude-gradient .oc-bb{background:rgba(88,166,255,.15);color:var(--oc-blue)}
|
||||
.tpl-obsidian-claude-gradient .oc-bg{background:rgba(63,185,80,.15);color:var(--oc-green)}
|
||||
.tpl-obsidian-claude-gradient .oc-bo{background:rgba(249,115,22,.15);color:var(--oc-orange)}
|
||||
.tpl-obsidian-claude-gradient .oc-code{background:#010409;border:1px solid var(--oc-border);border-radius:12px;padding:20px 24px;font-family:'JetBrains Mono',monospace;font-size:14px;line-height:1.85;width:100%;max-width:860px;text-align:left;color:#e6edf3}
|
||||
.tpl-obsidian-claude-gradient .oc-code .cp{color:var(--oc-green)}
|
||||
.tpl-obsidian-claude-gradient .oc-code .cc{color:var(--oc-blue)}
|
||||
.tpl-obsidian-claude-gradient .oc-code .ca{color:var(--oc-accent3)}
|
||||
.tpl-obsidian-claude-gradient .oc-code .cm{color:var(--oc-dimmer)}
|
||||
.tpl-obsidian-claude-gradient .oc-code .cs{color:var(--oc-orange)}
|
||||
.tpl-obsidian-claude-gradient .oc-hl{background:rgba(124,58,237,.1);border:1px solid rgba(168,85,247,.3);border-left:4px solid var(--oc-accent2);border-radius:0 12px 12px 0;padding:16px 22px;font-size:16px;line-height:1.7;max-width:860px;text-align:left}
|
||||
.tpl-obsidian-claude-gradient .oc-steps{display:flex;flex-direction:column;gap:0;width:100%;max-width:820px;text-align:left}
|
||||
.tpl-obsidian-claude-gradient .oc-step{display:flex;gap:20px;align-items:flex-start;padding:18px 0;border-bottom:1px solid var(--oc-border)}
|
||||
.tpl-obsidian-claude-gradient .oc-step:last-child{border-bottom:none}
|
||||
.tpl-obsidian-claude-gradient .oc-sn{width:36px;height:36px;flex-shrink:0;border-radius:50%;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:15px;background:linear-gradient(135deg,var(--oc-accent),var(--oc-blue));color:#fff}
|
||||
.tpl-obsidian-claude-gradient .oc-sc h4{font-size:17px;font-weight:600;margin-bottom:6px;color:var(--oc-text)}
|
||||
.tpl-obsidian-claude-gradient .oc-sc p{font-size:14px;color:var(--oc-dim);line-height:1.6}
|
||||
.tpl-obsidian-claude-gradient .oc-pill{display:inline-flex;align-items:center;gap:8px;background:var(--oc-surface2);border:1px solid var(--oc-border);border-radius:999px;padding:7px 18px;font-size:14px;font-weight:500;color:var(--oc-text);margin:4px 6px 4px 0}
|
||||
.tpl-obsidian-claude-gradient .oc-quote{max-width:800px}
|
||||
.tpl-obsidian-claude-gradient .oc-quote blockquote{font-size:26px;font-weight:500;line-height:1.6;position:relative;padding:0 36px;margin:0;color:var(--oc-text)}
|
||||
.tpl-obsidian-claude-gradient .oc-quote blockquote::before{content:'"';position:absolute;left:-6px;top:-22px;font-size:78px;color:var(--oc-accent);opacity:.4;font-family:Georgia,serif;line-height:1}
|
||||
.tpl-obsidian-claude-gradient .oc-quote .attr{margin-top:20px;font-size:13px;color:var(--oc-dim)}
|
||||
.tpl-obsidian-claude-gradient .oc-big{font-size:140px;font-weight:900;line-height:.95;letter-spacing:-.04em}
|
||||
|
||||
</style>
|
||||
<style>
|
||||
/* Static-preview fallback (runtime.js is absent — keep every slide visible) */
|
||||
.deck{height:auto;min-height:100vh;overflow:visible}
|
||||
.slide{position:relative;inset:auto;opacity:1;pointer-events:auto;transform:none;height:100vh;page-break-after:always}
|
||||
.deck-header,.deck-footer,.slide-number,.progress-bar,.notes-overlay,.overview{pointer-events:none}
|
||||
.notes{display:none!important}
|
||||
</style></head>
|
||||
<body class="tpl-obsidian-claude-gradient">
|
||||
<div class="deck">
|
||||
|
||||
<!-- 1. COVER -->
|
||||
<section class="slide is-active">
|
||||
<div class="oc-cbg"></div>
|
||||
<div class="oc-cgrid"></div>
|
||||
<div class="oc-snum">01 / 08</div>
|
||||
<div class="oc-tag">● OBSIDIAN × CLAUDE · 第二大脑</div>
|
||||
<h1 class="oc-h1">把 Obsidian 和 Claude<br>拧成 <span class="oc-g">一条神经</span></h1>
|
||||
<p class="oc-sub">不是又一个 AI 笔记插件 —— 是让 Claude 真正理解你 vault 的结构、链接、双向引用,<br>然后在你想写东西之前就把资料准备好。</p>
|
||||
<div style="margin-top:32px">
|
||||
<span class="oc-pill">🧠 Markdown-native</span>
|
||||
<span class="oc-pill">⚡ MCP-ready</span>
|
||||
<span class="oc-pill">🔗 双链理解</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 2. SECTION -->
|
||||
<section class="slide">
|
||||
<div class="oc-cbg"></div>
|
||||
<div class="oc-cgrid"></div>
|
||||
<div class="oc-snum">02 / 08</div>
|
||||
<div class="oc-tag">● CHAPTER 01</div>
|
||||
<h1 class="oc-h1" style="font-size:110px">Why <span class="oc-g">not</span> Notion?</h1>
|
||||
<p class="oc-sub">当你的知识多到会互相引用时,<br>「文件夹」就不够了,「数据库」也不是答案。</p>
|
||||
</section>
|
||||
|
||||
<!-- 3. CONTENT — compare cards -->
|
||||
<section class="slide">
|
||||
<div class="oc-cbg"></div>
|
||||
<div class="oc-cgrid"></div>
|
||||
<div class="oc-snum">03 / 08</div>
|
||||
<div class="oc-tag">● COMPARE</div>
|
||||
<h2 class="oc-h2">Notion vs <span class="oc-g">Obsidian</span> · 对 AI 友好度</h2>
|
||||
<div class="oc-grid-2">
|
||||
<div class="oc-card">
|
||||
<span class="oc-badge oc-bb">NOTION</span>
|
||||
<h4 style="font-size:20px;margin-bottom:10px">数据库原生</h4>
|
||||
<p style="color:var(--oc-dim);font-size:14px;line-height:1.65">适合结构化任务、团队协作,但是——<br>• AI 要走 API,拿不到实时全文<br>• 嵌套块结构复杂,token 成本高<br>• 本地化差,没法当长期记忆</p>
|
||||
</div>
|
||||
<div class="oc-card" style="border-color:rgba(168,85,247,.35);background:rgba(124,58,237,.05)">
|
||||
<span class="oc-badge oc-bp">OBSIDIAN</span>
|
||||
<h4 style="font-size:20px;margin-bottom:10px">纯 Markdown + 双链</h4>
|
||||
<p style="color:var(--oc-dim);font-size:14px;line-height:1.65">对 AI 天生友好 ——<br>• 所有东西就是文件,Claude 直接读<br>• 双链 = 天然 graph,抽实体几乎零成本<br>• 离线、可 git、可 diff、可回滚</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oc-hl" style="margin-top:26px">💡 <b>关键洞察:</b>AI 不需要「更聪明的数据库」,它需要「能被它自己读懂的文件系统」。</div>
|
||||
</section>
|
||||
|
||||
<!-- 4. STEPS -->
|
||||
<section class="slide">
|
||||
<div class="oc-cbg"></div>
|
||||
<div class="oc-cgrid"></div>
|
||||
<div class="oc-snum">04 / 08</div>
|
||||
<div class="oc-tag">● SETUP · 4 STEPS</div>
|
||||
<h2 class="oc-h2">从 0 到第一次「AI 写笔记」</h2>
|
||||
<div class="oc-steps">
|
||||
<div class="oc-step"><div class="oc-sn">1</div><div class="oc-sc"><h4>装 Obsidian + 开 Local REST API 插件</h4><p>社区插件,一个勾就开。它让外部进程能 read/write 你的 vault。</p></div></div>
|
||||
<div class="oc-step"><div class="oc-sn">2</div><div class="oc-sc"><h4>接 Claude Desktop + obsidian-mcp server</h4><p>MCP 一个配置文件就能接,token 填 vault 的 api key。</p></div></div>
|
||||
<div class="oc-step"><div class="oc-sn">3</div><div class="oc-sc"><h4>装 5 个 obsidian-skills</h4><p>markdown / bases / canvas / cli / defuddle —— 让 Claude 知道怎么正确使用 Obsidian。</p></div></div>
|
||||
<div class="oc-step"><div class="oc-sn">4</div><div class="oc-sc"><h4>让 Claude 自己整理一次</h4><p>「帮我把最近 10 篇笔记里的重复概念合并,生成一张新的 MOC」—— 90 秒出结果。</p></div></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 5. CODE -->
|
||||
<section class="slide">
|
||||
<div class="oc-cbg"></div>
|
||||
<div class="oc-cgrid"></div>
|
||||
<div class="oc-snum">05 / 08</div>
|
||||
<div class="oc-tag">● MCP CONFIG</div>
|
||||
<h2 class="oc-h2">claude_desktop_config.json</h2>
|
||||
<pre class="oc-code"><span class="cm">// ~/Library/Application Support/Claude/claude_desktop_config.json</span>
|
||||
{
|
||||
<span class="cc">"mcpServers"</span>: {
|
||||
<span class="cc">"obsidian"</span>: {
|
||||
<span class="cc">"command"</span>: <span class="cs">"npx"</span>,
|
||||
<span class="cc">"args"</span>: [<span class="cs">"-y"</span>, <span class="cs">"@modelcontextprotocol/server-obsidian"</span>],
|
||||
<span class="cc">"env"</span>: {
|
||||
<span class="cc">"OBSIDIAN_API_KEY"</span>: <span class="cs">"xxxxxxxxxxxxxxxx"</span>,
|
||||
<span class="cc">"OBSIDIAN_HOST"</span>: <span class="cs">"http://127.0.0.1:27123"</span>
|
||||
}
|
||||
}
|
||||
}
|
||||
}</pre>
|
||||
<p class="oc-sub" style="margin-top:18px">重启 Claude Desktop,输入 <b style="color:var(--oc-accent3)">/mcp</b>,你会看到 obsidian 已连。</p>
|
||||
</section>
|
||||
|
||||
<!-- 6. STATS -->
|
||||
<section class="slide">
|
||||
<div class="oc-cbg"></div>
|
||||
<div class="oc-cgrid"></div>
|
||||
<div class="oc-snum">06 / 08</div>
|
||||
<div class="oc-tag">● 3 MONTHS IN</div>
|
||||
<h2 class="oc-h2">跑了 90 天,我的 <span class="oc-g">vault 数据</span></h2>
|
||||
<div class="oc-grid-3" style="margin-top:28px">
|
||||
<div class="oc-card" style="text-align:center"><div class="oc-big oc-g" style="font-size:80px">1,842</div><p style="color:var(--oc-dim);margin-top:8px;font-size:13px">notes in vault</p></div>
|
||||
<div class="oc-card" style="text-align:center"><div class="oc-big oc-g" style="font-size:80px">6.3k</div><p style="color:var(--oc-dim);margin-top:8px;font-size:13px">backlinks (由 AI 自动补)</p></div>
|
||||
<div class="oc-card" style="text-align:center"><div class="oc-big oc-g" style="font-size:80px">-74%</div><p style="color:var(--oc-dim);margin-top:8px;font-size:13px">找资料平均耗时</p></div>
|
||||
</div>
|
||||
<div class="oc-hl" style="margin-top:26px">最大收益不是「AI 帮我写」,而是「AI 帮我把旧笔记重新连起来」—— 每周 30 分钟,vault 就会主动生长。</div>
|
||||
</section>
|
||||
|
||||
<!-- 7. QUOTE / CTA -->
|
||||
<section class="slide">
|
||||
<div class="oc-cbg"></div>
|
||||
<div class="oc-cgrid"></div>
|
||||
<div class="oc-snum">07 / 08</div>
|
||||
<div class="oc-tag">● CTA · 今晚可以做</div>
|
||||
<div class="oc-quote">
|
||||
<blockquote>不要再找「AI 笔记应用」了。<br>你要的是一个 <span class="oc-g">文件夹 + 一条神经</span>。</blockquote>
|
||||
<div class="attr">— 我自己,用了 90 天后</div>
|
||||
</div>
|
||||
<div style="margin-top:36px">
|
||||
<span class="oc-pill">⬇ obsidian.md</span>
|
||||
<span class="oc-pill">⬇ Claude Desktop</span>
|
||||
<span class="oc-pill">⬇ obsidian-mcp</span>
|
||||
<span class="oc-pill">⬇ obsidian-skills × 5</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 8. THANKS -->
|
||||
<section class="slide">
|
||||
<div class="oc-cbg"></div>
|
||||
<div class="oc-cgrid"></div>
|
||||
<div class="oc-snum">08 / 08</div>
|
||||
<div class="oc-big oc-g">Thanks.</div>
|
||||
<p class="oc-sub" style="margin-top:26px">配置模板、skill manifest、我的 vault 结构图都在 <b style="color:var(--oc-accent3)">github.com/lewis/obsidian-claude</b></p>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
79
skills/html-ppt-pitch-deck/SKILL.md
Normal file
79
skills/html-ppt-pitch-deck/SKILL.md
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
---
|
||||
name: html-ppt-pitch-deck
|
||||
description: Investor-ready 10-slide HTML pitch deck — white + blue→purple gradient hero, big numbers, traction bar chart, $4.5M-style ask page. Use when the user wants a fundraising deck, seed-round pitch, or VC meeting slides.
|
||||
triggers:
|
||||
- "pitch deck"
|
||||
- "pitch"
|
||||
- "fundraising"
|
||||
- "seed round"
|
||||
- "investor deck"
|
||||
- "vc deck"
|
||||
- "pitch slides"
|
||||
od:
|
||||
mode: deck
|
||||
scenario: finance
|
||||
featured: 20
|
||||
upstream: "https://github.com/lewislulu/html-ppt-skill"
|
||||
preview:
|
||||
type: html
|
||||
entry: index.html
|
||||
design_system:
|
||||
requires: false
|
||||
speaker_notes: true
|
||||
animations: true
|
||||
example_prompt: "Build a 10-slide pitch deck in HTML for my seed round. Use the html-ppt-pitch-deck full-deck template (white + blue→purple gradient, traction bars, $X.XM ask). Confirm three things first: (1) name + one-line pitch, (2) key traction numbers, (3) ask + use of funds."
|
||||
---
|
||||
# HTML PPT · Pitch Deck
|
||||
|
||||
A focused entry point into the [`html-ppt`](../html-ppt/SKILL.md) master skill that lands the user directly on the **`pitch-deck`** full-deck template.
|
||||
|
||||
## When this card is picked
|
||||
|
||||
The Examples gallery wires "Use this prompt" to the example_prompt above. When you accept that prompt, this card is the right pick if the user wants exactly the visual identity of `pitch-deck` (see the upstream [full-decks catalog](../html-ppt/references/full-decks.md) for screenshots and rationale).
|
||||
|
||||
## How to author the deck
|
||||
|
||||
1. **Read the master skill first.** All authoring rules live in
|
||||
[`skills/html-ppt/SKILL.md`](../html-ppt/SKILL.md) — content/audience checklist,
|
||||
token rules, layout reuse, presenter mode, the keyboard runtime, and the
|
||||
"never put presenter-only text on the slide" rule.
|
||||
2. **Start from the matching template folder:**
|
||||
`skills/html-ppt/templates/full-decks/pitch-deck/` — copy `index.html` and
|
||||
`style.css` into the project, keep the `.tpl-pitch-deck` body class.
|
||||
3. **Bring the shared runtime with the template.** The upstream
|
||||
`index.html` links the shared CSS/JS via `../../../assets/...` because it
|
||||
sits three folders deep inside `skills/html-ppt/templates/full-decks/`.
|
||||
Once you copy `index.html` into the project, those parent-relative URLs
|
||||
no longer resolve and `base.css`, `animations.css`, and `runtime.js`
|
||||
will 404 — meaning the deck never activates and slide navigation is
|
||||
dead. Pick one of these two recipes per project:
|
||||
- **Recipe A — copy + rewrite (preferred):** copy
|
||||
`skills/html-ppt/assets/fonts.css`, `skills/html-ppt/assets/base.css`,
|
||||
`skills/html-ppt/assets/animations/animations.css`, and
|
||||
`skills/html-ppt/assets/runtime.js` into a project-local
|
||||
`assets/` (with `assets/animations/animations.css`), then rewrite the
|
||||
four `<link>`/`<script>` tags in `index.html` from
|
||||
`../../../assets/...` to the matching project-local paths
|
||||
(`assets/fonts.css`, `assets/base.css`,
|
||||
`assets/animations/animations.css`, `assets/runtime.js`).
|
||||
- **Recipe B — inline:** read the same four files and replace each
|
||||
`<link rel="stylesheet" href="../../../assets/...">` with a
|
||||
`<style>...</style>` containing the file's contents, and the
|
||||
`<script src="../../../assets/runtime.js">` with a
|
||||
`<script>...</script>` containing `runtime.js`. Yields a single
|
||||
self-contained `index.html`.
|
||||
Either way, do not ship the upstream `../../../assets/...` URLs
|
||||
verbatim into a project artifact — they only work in-tree.
|
||||
4. **Pick a theme.** Default tokens look fine; if the user wants a different
|
||||
feel, swap in any of the 36 themes from `skills/html-ppt/assets/themes/*.css`
|
||||
via `<link id="theme-link">` and let `T` cycle.
|
||||
5. **Replace demo content, not classes.** The `.tpl-pitch-deck` scoped CSS only
|
||||
recognises the structural classes shipped in the template — keep them.
|
||||
6. **Speaker notes go inside `<aside class="notes">` or `<div class="notes">`** — never as visible text on the slide.
|
||||
|
||||
## Attribution
|
||||
|
||||
Visual system, layouts, themes and the runtime keyboard model come from
|
||||
the upstream MIT-licensed [`lewislulu/html-ppt-skill`](https://github.com/lewislulu/html-ppt-skill). The
|
||||
LICENSE file ships at `skills/html-ppt/LICENSE`; please keep it in place when
|
||||
redistributing.
|
||||
495
skills/html-ppt-pitch-deck/example.html
Normal file
495
skills/html-ppt-pitch-deck/example.html
Normal file
|
|
@ -0,0 +1,495 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Lumen · Pitch Deck</title>
|
||||
<style>/* html-ppt :: shared webfonts */
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@200;300;400;500;600;700;800;900&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@200;300;400;500;600;700;900&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@300;400;600;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,600;0,800;1,400&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@300;400;500;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Archivo+Black&display=swap');
|
||||
|
||||
</style>
|
||||
<style>/* html-ppt :: base.css — reset + shared tokens + layout primitives */
|
||||
/* Default tokens. Themes in assets/themes/*.css override the :root block. */
|
||||
:root {
|
||||
--bg: #ffffff;
|
||||
--bg-soft: #f7f7f8;
|
||||
--surface: #ffffff;
|
||||
--surface-2: #f2f2f4;
|
||||
--border: rgba(0,0,0,.08);
|
||||
--border-strong: rgba(0,0,0,.16);
|
||||
--text-1: #111216;
|
||||
--text-2: #55596a;
|
||||
--text-3: #8a8f9e;
|
||||
--accent: #3b6cff;
|
||||
--accent-2: #7a5cff;
|
||||
--accent-3: #ff5c8a;
|
||||
--good: #1aaf6c;
|
||||
--warn: #f5a524;
|
||||
--bad: #e0445a;
|
||||
--grad: linear-gradient(135deg,#3b6cff,#7a5cff 55%,#ff5c8a);
|
||||
--grad-soft: linear-gradient(135deg,#eef2ff,#f5ecff 55%,#ffeef5);
|
||||
--radius: 18px;
|
||||
--radius-sm: 12px;
|
||||
--radius-lg: 26px;
|
||||
--shadow: 0 10px 30px rgba(18,24,40,.08), 0 2px 6px rgba(18,24,40,.04);
|
||||
--shadow-lg: 0 24px 60px rgba(18,24,40,.14), 0 6px 16px rgba(18,24,40,.06);
|
||||
--font-sans: 'Inter','Noto Sans SC',-apple-system,BlinkMacSystemFont,Helvetica,Arial,sans-serif;
|
||||
--font-serif: 'Playfair Display','Noto Serif SC',Georgia,serif;
|
||||
--font-mono: 'JetBrains Mono','IBM Plex Mono',SFMono-Regular,Menlo,monospace;
|
||||
--font-display: var(--font-sans);
|
||||
--letter-tight: -.03em;
|
||||
--letter-normal: -.01em;
|
||||
--ease: cubic-bezier(.4,0,.2,1);
|
||||
}
|
||||
|
||||
*,*::before,*::after{box-sizing:border-box}
|
||||
html,body{margin:0;padding:0;background:var(--bg);color:var(--text-1);
|
||||
font-family:var(--font-sans);font-weight:400;line-height:1.6;
|
||||
-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;
|
||||
letter-spacing:var(--letter-normal)}
|
||||
img,svg,video{max-width:100%;display:block}
|
||||
a{color:var(--accent);text-decoration:none}
|
||||
a:hover{text-decoration:underline}
|
||||
code,kbd,pre,samp{font-family:var(--font-mono)}
|
||||
|
||||
/* ================= SLIDE SYSTEM ================= */
|
||||
.deck{position:relative;width:100vw;height:100vh;overflow:hidden;background:var(--bg)}
|
||||
.slide{
|
||||
position:absolute;inset:0;
|
||||
display:flex;flex-direction:column;justify-content:center;
|
||||
padding:72px 96px;
|
||||
box-sizing:border-box;
|
||||
opacity:0;pointer-events:none;
|
||||
transition:opacity .5s var(--ease), transform .5s var(--ease);
|
||||
transform:translateX(30px);
|
||||
overflow:hidden;
|
||||
}
|
||||
.slide.is-active{opacity:1;pointer-events:auto;transform:translateX(0);z-index:2}
|
||||
.slide.is-prev{transform:translateX(-30px)}
|
||||
|
||||
/* single-page standalone (used when a layout file is opened directly) */
|
||||
body.single .slide{position:relative;width:100vw;height:100vh;opacity:1;transform:none;pointer-events:auto}
|
||||
|
||||
/* ================= TYPOGRAPHY ================= */
|
||||
.eyebrow{font-size:13px;font-weight:500;letter-spacing:.16em;text-transform:uppercase;color:var(--text-3)}
|
||||
.kicker{font-size:14px;font-weight:600;color:var(--accent);letter-spacing:.08em;text-transform:uppercase}
|
||||
h1.title,.h1{font-family:var(--font-display);font-size:72px;line-height:1.05;font-weight:800;letter-spacing:var(--letter-tight);margin:0 0 18px;color:var(--text-1)}
|
||||
h2.title,.h2{font-family:var(--font-display);font-size:54px;line-height:1.1;font-weight:700;letter-spacing:var(--letter-tight);margin:0 0 14px}
|
||||
h3,.h3{font-size:32px;line-height:1.2;font-weight:600;letter-spacing:var(--letter-normal);margin:0 0 10px}
|
||||
h4,.h4{font-size:22px;line-height:1.3;font-weight:600;margin:0 0 8px}
|
||||
.lede{font-size:22px;line-height:1.55;color:var(--text-2);font-weight:300;max-width:62ch}
|
||||
.dim{color:var(--text-2)}
|
||||
.dim2{color:var(--text-3)}
|
||||
.mono{font-family:var(--font-mono)}
|
||||
.serif{font-family:var(--font-serif)}
|
||||
.gradient-text{background:var(--grad);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;color:transparent}
|
||||
|
||||
/* ================= LAYOUT PRIMITIVES ================= */
|
||||
.stack>*+*{margin-top:14px}
|
||||
.row{display:flex;gap:24px;align-items:center}
|
||||
.row.wrap{flex-wrap:wrap}
|
||||
.grid{display:grid;gap:24px}
|
||||
.g2{grid-template-columns:repeat(2,1fr)}
|
||||
.g3{grid-template-columns:repeat(3,1fr)}
|
||||
.g4{grid-template-columns:repeat(4,1fr)}
|
||||
.center{display:flex;align-items:center;justify-content:center;text-align:center}
|
||||
.fill{flex:1}
|
||||
.sp-t{padding-top:24px}.sp-b{padding-bottom:24px}
|
||||
.mt-s{margin-top:8px}.mt-m{margin-top:18px}.mt-l{margin-top:32px}
|
||||
.mb-s{margin-bottom:8px}.mb-m{margin-bottom:18px}.mb-l{margin-bottom:32px}
|
||||
|
||||
/* ================= CARDS ================= */
|
||||
.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);
|
||||
padding:26px 28px;box-shadow:var(--shadow);position:relative;overflow:hidden}
|
||||
.card-soft{background:var(--surface-2);border:1px solid var(--border)}
|
||||
.card-outline{background:transparent;border:1.5px solid var(--border-strong);box-shadow:none}
|
||||
.card-accent{background:var(--surface);border-top:3px solid var(--accent)}
|
||||
.card-hover{transition:transform .3s var(--ease),box-shadow .3s var(--ease)}
|
||||
.card-hover:hover{transform:translateY(-4px);box-shadow:var(--shadow-lg)}
|
||||
|
||||
.pill{display:inline-block;padding:4px 12px;border-radius:999px;font-size:12px;font-weight:500;
|
||||
background:var(--surface-2);color:var(--text-2);border:1px solid var(--border)}
|
||||
.pill-accent{background:color-mix(in srgb,var(--accent) 12%,transparent);color:var(--accent);border-color:color-mix(in srgb,var(--accent) 28%,transparent)}
|
||||
|
||||
/* ================= BARS / DIVIDERS ================= */
|
||||
.divider{height:1px;background:var(--border);width:100%}
|
||||
.divider-accent{height:3px;width:72px;background:var(--accent);border-radius:2px}
|
||||
|
||||
/* ================= CHROME (header/footer/progress) ================= */
|
||||
.deck-header{position:absolute;top:24px;left:40px;right:40px;display:flex;align-items:center;justify-content:space-between;
|
||||
font-size:12px;color:var(--text-3);letter-spacing:.12em;text-transform:uppercase;z-index:10;pointer-events:none}
|
||||
.deck-footer{position:absolute;bottom:24px;left:40px;right:40px;display:flex;align-items:center;justify-content:space-between;
|
||||
font-size:12px;color:var(--text-3);z-index:10;pointer-events:none}
|
||||
.slide-number::before{content:attr(data-current)}
|
||||
.slide-number::after{content:" / " attr(data-total)}
|
||||
.progress-bar{position:fixed;left:0;right:0;bottom:0;height:3px;background:transparent;z-index:20}
|
||||
.progress-bar > span{display:block;height:100%;width:0;background:var(--accent);transition:width .3s var(--ease)}
|
||||
|
||||
/* ================= PRESENTER / OVERVIEW ================= */
|
||||
.notes{display:none!important}
|
||||
.notes-overlay{position:fixed;inset:auto 0 0 0;max-height:42vh;background:rgba(20,22,30,.95);color:#e8ebf4;
|
||||
padding:20px 32px;font-size:16px;line-height:1.6;border-top:1px solid rgba(255,255,255,.1);transform:translateY(100%);
|
||||
transition:transform .3s var(--ease);z-index:40;overflow:auto;font-family:var(--font-sans)}
|
||||
.notes-overlay.open{transform:translateY(0)}
|
||||
.overview{position:fixed;inset:0;background:rgba(10,12,18,.92);backdrop-filter:blur(12px);z-index:50;
|
||||
display:none;padding:40px;overflow:auto}
|
||||
.overview.open{display:grid;grid-template-columns:repeat(4,1fr);gap:20px;align-content:start}
|
||||
.overview .thumb{background:var(--surface);border:1px solid var(--border);border-radius:12px;
|
||||
aspect-ratio:16/9;overflow:hidden;cursor:pointer;position:relative;color:var(--text-1);padding:16px;
|
||||
font-size:11px;transition:transform .2s var(--ease)}
|
||||
.overview .thumb:hover{transform:scale(1.04)}
|
||||
.overview .thumb .n{position:absolute;top:8px;left:10px;font-weight:700;font-size:14px;color:var(--text-3)}
|
||||
.overview .thumb .t{position:absolute;bottom:10px;left:14px;right:14px;font-weight:600;color:var(--text-1)}
|
||||
|
||||
/* ================= PRESENTER VIEW ================= */
|
||||
/* Presenter view opens in a separate popup window (S key).
|
||||
* All presenter styles are self-contained in the popup HTML generated by runtime.js.
|
||||
* The audience window (this file) is NOT affected — it stays as normal deck view.
|
||||
* Only the .notes class below is needed to hide speaker notes from audience. */
|
||||
|
||||
/* ================= UTILITY ================= */
|
||||
.hidden{display:none!important}
|
||||
.nowrap{white-space:nowrap}
|
||||
.tr{text-align:right}.tc{text-align:center}.tl{text-align:left}
|
||||
.uppercase{text-transform:uppercase;letter-spacing:.12em}
|
||||
|
||||
/* ================= PRINT ================= */
|
||||
@media print{
|
||||
.slide{position:relative;opacity:1!important;transform:none!important;page-break-after:always;height:100vh}
|
||||
.deck-header,.deck-footer,.progress-bar,.notes-overlay,.overview{display:none!important}
|
||||
}
|
||||
|
||||
</style>
|
||||
<style>/* html-ppt :: animations.css
|
||||
* Apply by adding class="anim-<name>" or data-anim="<name>".
|
||||
* Durations are deliberately snappy; tweak --anim-dur per element.
|
||||
*/
|
||||
:root{--anim-dur:.7s;--anim-ease:cubic-bezier(.4,0,.2,1)}
|
||||
|
||||
/* ---------- FADE DIRECTIONALS ---------- */
|
||||
@keyframes kf-fade-up{from{opacity:0;transform:translateY(32px)}to{opacity:1;transform:none}}
|
||||
@keyframes kf-fade-down{from{opacity:0;transform:translateY(-32px)}to{opacity:1;transform:none}}
|
||||
@keyframes kf-fade-left{from{opacity:0;transform:translateX(-40px)}to{opacity:1;transform:none}}
|
||||
@keyframes kf-fade-right{from{opacity:0;transform:translateX(40px)}to{opacity:1;transform:none}}
|
||||
.anim-fade-up{animation:kf-fade-up var(--anim-dur) var(--anim-ease) both}
|
||||
.anim-fade-down{animation:kf-fade-down var(--anim-dur) var(--anim-ease) both}
|
||||
.anim-fade-left{animation:kf-fade-left var(--anim-dur) var(--anim-ease) both}
|
||||
.anim-fade-right{animation:kf-fade-right var(--anim-dur) var(--anim-ease) both}
|
||||
|
||||
/* ---------- RISE / DROP / ZOOM / BLUR / GLITCH ---------- */
|
||||
@keyframes kf-rise{from{opacity:0;transform:translateY(60px) scale(.97);filter:blur(6px)}to{opacity:1;transform:none;filter:none}}
|
||||
@keyframes kf-drop{from{opacity:0;transform:translateY(-60px) scale(.97)}to{opacity:1;transform:none}}
|
||||
@keyframes kf-zoom{0%{opacity:0;transform:scale(.6)}60%{transform:scale(1.04)}100%{opacity:1;transform:scale(1)}}
|
||||
@keyframes kf-blur{from{opacity:0;filter:blur(18px)}to{opacity:1;filter:none}}
|
||||
@keyframes kf-glitch{0%{opacity:0;transform:translateX(0);clip-path:inset(0 0 0 0)}
|
||||
20%{opacity:1;transform:translateX(-6px);clip-path:inset(20% 0 30% 0)}
|
||||
40%{transform:translateX(4px);clip-path:inset(50% 0 10% 0)}
|
||||
60%{transform:translateX(-3px);clip-path:inset(10% 0 60% 0)}
|
||||
80%{transform:translateX(2px);clip-path:inset(0 0 0 0)}
|
||||
100%{opacity:1;transform:none}}
|
||||
.anim-rise-in{animation:kf-rise .9s var(--anim-ease) both}
|
||||
.anim-drop-in{animation:kf-drop .8s var(--anim-ease) both}
|
||||
.anim-zoom-pop{animation:kf-zoom .7s cubic-bezier(.22,1.3,.36,1) both}
|
||||
.anim-blur-in{animation:kf-blur .8s var(--anim-ease) both}
|
||||
.anim-glitch-in{animation:kf-glitch .8s steps(5,end) both}
|
||||
|
||||
/* ---------- TYPEWRITER ---------- */
|
||||
.anim-typewriter{display:inline-block;overflow:hidden;white-space:nowrap;border-right:2px solid currentColor;
|
||||
width:0;animation:kf-type 2.4s steps(40,end) forwards, kf-caret 1s step-end infinite}
|
||||
@keyframes kf-type{to{width:100%}}
|
||||
@keyframes kf-caret{50%{border-color:transparent}}
|
||||
|
||||
/* ---------- GLOW / SHIMMER / GRADIENT-FLOW ---------- */
|
||||
@keyframes kf-neon{0%,100%{text-shadow:0 0 8px var(--accent),0 0 20px var(--accent)}
|
||||
50%{text-shadow:0 0 16px var(--accent),0 0 40px var(--accent),0 0 80px var(--accent)}}
|
||||
.anim-neon-glow{animation:kf-neon 2s ease-in-out infinite}
|
||||
|
||||
.anim-shimmer-sweep{position:relative;overflow:hidden}
|
||||
.anim-shimmer-sweep::after{content:"";position:absolute;inset:0;
|
||||
background:linear-gradient(110deg,transparent 40%,rgba(255,255,255,.55) 50%,transparent 60%);
|
||||
transform:translateX(-100%);animation:kf-shimmer 2.4s var(--anim-ease) infinite}
|
||||
@keyframes kf-shimmer{to{transform:translateX(100%)}}
|
||||
|
||||
.anim-gradient-flow{background:linear-gradient(90deg,var(--accent),var(--accent-2,var(--accent)),var(--accent-3,var(--accent)),var(--accent));
|
||||
background-size:300% 100%;-webkit-background-clip:text;background-clip:text;color:transparent;-webkit-text-fill-color:transparent;
|
||||
animation:kf-gradflow 4s linear infinite}
|
||||
@keyframes kf-gradflow{to{background-position:300% 0}}
|
||||
|
||||
/* ---------- STAGGER LIST ---------- */
|
||||
.anim-stagger-list > *{opacity:0;animation:kf-rise .65s var(--anim-ease) both}
|
||||
.anim-stagger-list > *:nth-child(1){animation-delay:.05s}
|
||||
.anim-stagger-list > *:nth-child(2){animation-delay:.15s}
|
||||
.anim-stagger-list > *:nth-child(3){animation-delay:.25s}
|
||||
.anim-stagger-list > *:nth-child(4){animation-delay:.35s}
|
||||
.anim-stagger-list > *:nth-child(5){animation-delay:.45s}
|
||||
.anim-stagger-list > *:nth-child(6){animation-delay:.55s}
|
||||
.anim-stagger-list > *:nth-child(7){animation-delay:.65s}
|
||||
.anim-stagger-list > *:nth-child(8){animation-delay:.75s}
|
||||
.anim-stagger-list > *:nth-child(n+9){animation-delay:.85s}
|
||||
|
||||
/* ---------- COUNTER-UP (JS-driven, marker class only) ---------- */
|
||||
.counter{font-variant-numeric:tabular-nums}
|
||||
|
||||
/* ---------- SVG PATH DRAW ---------- */
|
||||
.anim-path-draw path,.anim-path-draw line,.anim-path-draw polyline,.anim-path-draw circle,.anim-path-draw rect{
|
||||
stroke-dasharray:1000;stroke-dashoffset:1000;animation:kf-draw 2s var(--anim-ease) forwards}
|
||||
@keyframes kf-draw{to{stroke-dashoffset:0}}
|
||||
|
||||
/* ---------- PARALLAX TILT (hover) ---------- */
|
||||
.anim-parallax-tilt{transform-style:preserve-3d;transition:transform .4s var(--anim-ease)}
|
||||
.anim-parallax-tilt:hover{transform:perspective(900px) rotateX(6deg) rotateY(-8deg) translateZ(10px)}
|
||||
|
||||
/* ---------- CARD FLIP 3D ---------- */
|
||||
@keyframes kf-flip{from{transform:perspective(1200px) rotateY(-90deg);opacity:0}
|
||||
to{transform:perspective(1200px) rotateY(0);opacity:1}}
|
||||
.anim-card-flip-3d{animation:kf-flip .9s var(--anim-ease) both;transform-style:preserve-3d;backface-visibility:hidden}
|
||||
|
||||
/* ---------- CUBE ROTATE 3D ---------- */
|
||||
@keyframes kf-cube{from{transform:perspective(1200px) rotateX(20deg) rotateY(-90deg) translateZ(-200px);opacity:0}
|
||||
to{transform:perspective(1200px) rotateX(0) rotateY(0) translateZ(0);opacity:1}}
|
||||
.anim-cube-rotate-3d{animation:kf-cube 1s var(--anim-ease) both}
|
||||
|
||||
/* ---------- PAGE TURN 3D ---------- */
|
||||
@keyframes kf-pageturn{from{transform:perspective(1600px) rotateY(-85deg);transform-origin:left center;opacity:0}
|
||||
to{transform:perspective(1600px) rotateY(0);opacity:1}}
|
||||
.anim-page-turn-3d{animation:kf-pageturn 1s var(--anim-ease) both;transform-origin:left center}
|
||||
|
||||
/* ---------- PERSPECTIVE ZOOM ---------- */
|
||||
@keyframes kf-pzoom{from{opacity:0;transform:perspective(1400px) translateZ(-400px) rotateX(12deg)}
|
||||
to{opacity:1;transform:none}}
|
||||
.anim-perspective-zoom{animation:kf-pzoom 1s var(--anim-ease) both}
|
||||
|
||||
/* ---------- MARQUEE SCROLL ---------- */
|
||||
.anim-marquee-scroll{display:flex;gap:48px;white-space:nowrap;animation:kf-marquee 20s linear infinite}
|
||||
@keyframes kf-marquee{from{transform:translateX(0)}to{transform:translateX(-50%)}}
|
||||
|
||||
/* ---------- KEN BURNS ---------- */
|
||||
@keyframes kf-kenburns{0%{transform:scale(1) translate(0,0)}100%{transform:scale(1.15) translate(-2%,-1%)}}
|
||||
.anim-kenburns{animation:kf-kenburns 14s ease-in-out infinite alternate}
|
||||
|
||||
/* ---------- CONFETTI BURST (pseudo — pure CSS sparkles) ---------- */
|
||||
.anim-confetti-burst{position:relative}
|
||||
.anim-confetti-burst::before,.anim-confetti-burst::after{
|
||||
content:"";position:absolute;top:50%;left:50%;width:8px;height:8px;border-radius:50%;
|
||||
background:var(--accent);box-shadow:
|
||||
20px -30px 0 var(--accent-2,var(--accent)),-25px -20px 0 var(--accent-3,var(--accent)),
|
||||
30px 20px 0 var(--good,#1aaf6c),-30px 25px 0 var(--warn,#f5a524),
|
||||
40px -10px 0 var(--bad,#e0445a),-45px 0 0 var(--accent),
|
||||
10px 40px 0 var(--accent-2,var(--accent)),-15px -40px 0 var(--accent-3,var(--accent));
|
||||
opacity:0;animation:kf-confetti 1.2s var(--anim-ease) forwards}
|
||||
.anim-confetti-burst::after{animation-delay:.15s;transform:rotate(45deg)}
|
||||
@keyframes kf-confetti{0%{opacity:0;transform:scale(.2)}30%{opacity:1}100%{opacity:0;transform:scale(2.2)}}
|
||||
|
||||
/* ---------- SPOTLIGHT ---------- */
|
||||
@keyframes kf-spot{0%{clip-path:circle(0% at 50% 50%)}100%{clip-path:circle(140% at 50% 50%)}}
|
||||
.anim-spotlight{animation:kf-spot 1.1s var(--anim-ease) both}
|
||||
|
||||
/* ---------- MORPH SHAPE (SVG) ---------- */
|
||||
.anim-morph-shape path{animation:kf-morph 6s ease-in-out infinite alternate}
|
||||
@keyframes kf-morph{0%{d:path("M60,120 Q120,20 180,120 T300,120")}
|
||||
100%{d:path("M60,120 Q120,220 180,120 T300,120")}}
|
||||
|
||||
/* ---------- RIPPLE REVEAL ---------- */
|
||||
@keyframes kf-ripple{0%{clip-path:circle(0% at 20% 80%);opacity:.4}
|
||||
100%{clip-path:circle(160% at 20% 80%);opacity:1}}
|
||||
.anim-ripple-reveal{animation:kf-ripple 1.2s var(--anim-ease) both}
|
||||
|
||||
/* reduced motion */
|
||||
@media (prefers-reduced-motion: reduce){
|
||||
[class*="anim-"]{animation:none!important;transition:none!important}
|
||||
}
|
||||
|
||||
</style>
|
||||
<style>/* pitch-deck — classic YC/VC pitch */
|
||||
.tpl-pitch-deck{
|
||||
--bg:#ffffff;--bg-soft:#f6f7fb;--surface:#ffffff;--surface-2:#f2f4fa;
|
||||
--border:rgba(20,25,60,.08);--border-strong:rgba(20,25,60,.18);
|
||||
--text-1:#0d1130;--text-2:#4a5070;--text-3:#8a90ad;
|
||||
--accent:#3b5bff;--accent-2:#7a46ff;--accent-3:#d94cff;
|
||||
--grad:linear-gradient(135deg,#3b5bff 0%,#7a46ff 55%,#d94cff 100%);
|
||||
--grad-soft:linear-gradient(135deg,#eef1ff,#f4edff 55%,#fbedff);
|
||||
--radius:20px;--radius-lg:28px;
|
||||
--shadow:0 14px 40px rgba(20,25,60,.08),0 2px 8px rgba(20,25,60,.04);
|
||||
font-family:'Inter','Noto Sans SC',sans-serif;
|
||||
}
|
||||
.tpl-pitch-deck .slide{padding:88px 112px}
|
||||
.tpl-pitch-deck .kicker{color:var(--accent);font-weight:700}
|
||||
.tpl-pitch-deck .h1{font-size:86px;line-height:1.02;font-weight:900;letter-spacing:-.035em}
|
||||
.tpl-pitch-deck .h2{font-size:62px;font-weight:800;letter-spacing:-.03em}
|
||||
.tpl-pitch-deck .mega{font-size:180px;font-weight:900;line-height:.95;letter-spacing:-.05em;background:var(--grad);-webkit-background-clip:text;background-clip:text;color:transparent}
|
||||
.tpl-pitch-deck .mega-sub{font-size:28px;color:var(--text-2);margin-top:18px}
|
||||
.tpl-pitch-deck .cover-bg{position:absolute;inset:0;background:var(--grad-soft);z-index:-1}
|
||||
.tpl-pitch-deck .cover-blob{position:absolute;right:-140px;top:-140px;width:560px;height:560px;border-radius:50%;background:var(--grad);filter:blur(8px);opacity:.35;z-index:-1}
|
||||
.tpl-pitch-deck .brand-dot{display:inline-block;width:14px;height:14px;border-radius:50%;background:var(--grad);margin-right:10px;vertical-align:middle}
|
||||
.tpl-pitch-deck .brand{font-weight:800;font-size:22px;letter-spacing:-.02em}
|
||||
.tpl-pitch-deck .card{border-radius:var(--radius)}
|
||||
.tpl-pitch-deck .num-tag{font-family:'Inter',sans-serif;font-size:14px;font-weight:700;color:var(--accent);letter-spacing:.12em}
|
||||
.tpl-pitch-deck .big-q{font-family:'Playfair Display',serif;font-size:56px;line-height:1.15;font-weight:700;letter-spacing:-.02em;max-width:22ch}
|
||||
.tpl-pitch-deck .metric{display:flex;flex-direction:column;gap:6px}
|
||||
.tpl-pitch-deck .metric .n{font-size:72px;font-weight:900;letter-spacing:-.035em;background:var(--grad);-webkit-background-clip:text;background-clip:text;color:transparent;line-height:1}
|
||||
.tpl-pitch-deck .metric .l{color:var(--text-2);font-size:16px}
|
||||
.tpl-pitch-deck .team-card{text-align:center;padding:32px 20px}
|
||||
.tpl-pitch-deck .avatar{width:96px;height:96px;border-radius:50%;margin:0 auto 14px;background:var(--grad);display:flex;align-items:center;justify-content:center;color:#fff;font-weight:800;font-size:32px}
|
||||
.tpl-pitch-deck .ask-box{background:var(--grad);color:#fff;padding:56px 64px;border-radius:var(--radius-lg);box-shadow:0 30px 70px rgba(59,91,255,.35)}
|
||||
.tpl-pitch-deck .ask-box .h2{color:#fff}
|
||||
.tpl-pitch-deck .ask-box .dim{color:rgba(255,255,255,.85)}
|
||||
.tpl-pitch-deck .traction-bar{display:flex;align-items:flex-end;gap:14px;height:240px;margin-top:24px}
|
||||
.tpl-pitch-deck .traction-bar .bar{flex:1;background:var(--grad);border-radius:8px 8px 0 0;position:relative;min-height:20px}
|
||||
.tpl-pitch-deck .traction-bar .bar span{position:absolute;bottom:-28px;left:0;right:0;text-align:center;font-size:13px;color:var(--text-3)}
|
||||
.tpl-pitch-deck .traction-bar .bar em{position:absolute;top:-28px;left:0;right:0;text-align:center;font-size:14px;font-weight:700;font-style:normal;color:var(--text-1)}
|
||||
.tpl-pitch-deck .section-num{font-size:220px;font-weight:900;line-height:.9;color:var(--surface-2);position:absolute;right:72px;bottom:40px;z-index:0;letter-spacing:-.05em}
|
||||
.tpl-pitch-deck .slide > *{position:relative;z-index:1}
|
||||
.tpl-pitch-deck .deck-footer{color:var(--text-3)}
|
||||
|
||||
</style>
|
||||
<style>
|
||||
/* Static-preview fallback (runtime.js is absent — keep every slide visible) */
|
||||
.deck{height:auto;min-height:100vh;overflow:visible}
|
||||
.slide{position:relative;inset:auto;opacity:1;pointer-events:auto;transform:none;height:100vh;page-break-after:always}
|
||||
.deck-header,.deck-footer,.slide-number,.progress-bar,.notes-overlay,.overview{pointer-events:none}
|
||||
.notes{display:none!important}
|
||||
</style></head>
|
||||
<body class="tpl-pitch-deck">
|
||||
<div class="deck">
|
||||
|
||||
<!-- 1. Cover -->
|
||||
<section class="slide" data-title="Cover">
|
||||
<div class="cover-bg"></div>
|
||||
<div class="cover-blob"></div>
|
||||
<div style="position:absolute;top:56px;left:112px"><span class="brand-dot"></span><span class="brand">Lumen</span></div>
|
||||
<p class="kicker">Seed round · 2026</p>
|
||||
<h1 class="h1 anim-fade-up" data-anim="fade-up">The operating system<br>for <span class="gradient-text">solo founders</span>.</h1>
|
||||
<p class="lede mt-m">One workspace for billing, CRM, contracts and taxes — built for the 70M people running a business of one.</p>
|
||||
<div class="deck-footer"><span>Maya Chen · CEO</span><span class="slide-number" data-current="1" data-total="10"></span></div>
|
||||
</section>
|
||||
|
||||
<!-- 2. Problem -->
|
||||
<section class="slide" data-title="Problem">
|
||||
<span class="section-num">01</span>
|
||||
<p class="num-tag">PROBLEM</p>
|
||||
<h2 class="h2 mt-s">Solo founders duct-tape<br>7+ tools to stay alive.</h2>
|
||||
<div class="grid g3 mt-l">
|
||||
<div class="card"><h4>Fragmentation</h4><p class="dim">Stripe, QuickBooks, HubSpot, DocuSign, Notion, Gusto, a spreadsheet. Nothing talks.</p></div>
|
||||
<div class="card"><h4>$480/mo wasted</h4><p class="dim">Average solo founder pays for 9 SaaS seats they only half-use.</p></div>
|
||||
<div class="card"><h4>14 hrs / week lost</h4><p class="dim">Copy-pasting between tools instead of selling.</p></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 3. Solution -->
|
||||
<section class="slide" data-title="Solution">
|
||||
<span class="section-num">02</span>
|
||||
<p class="num-tag">SOLUTION</p>
|
||||
<h2 class="h2 mt-s">Lumen is <span class="gradient-text">one spine</span><br>for the business of one.</h2>
|
||||
<p class="lede mt-m">Invoice a client → the payment lands → the tax is reserved → the contract is filed → your dashboard updates. In one app. Without plumbing.</p>
|
||||
<div class="row mt-l">
|
||||
<span class="pill pill-accent">Billing</span>
|
||||
<span class="pill pill-accent">CRM</span>
|
||||
<span class="pill pill-accent">Contracts</span>
|
||||
<span class="pill pill-accent">Taxes</span>
|
||||
<span class="pill pill-accent">Banking</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 4. Product -->
|
||||
<section class="slide" data-title="Product">
|
||||
<span class="section-num">03</span>
|
||||
<p class="num-tag">PRODUCT</p>
|
||||
<h2 class="h2 mt-s">Built around "jobs to be done".</h2>
|
||||
<div class="grid g2 mt-l">
|
||||
<div class="card card-hover"><h4>Get paid</h4><p class="dim">Invoices, subscriptions and Stripe/Wise payouts with a single click. ACH, card, wire, crypto.</p></div>
|
||||
<div class="card card-hover"><h4>Stay legal</h4><p class="dim">E-sign contracts from templates. Auto-file 1099s and quarterly estimates.</p></div>
|
||||
<div class="card card-hover"><h4>Sell smarter</h4><p class="dim">Lead inbox, pipeline, email sequences. No separate CRM.</p></div>
|
||||
<div class="card card-hover"><h4>See the business</h4><p class="dim">Live P&L, runway, top customers, churn. One dashboard, zero spreadsheets.</p></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 5. Market -->
|
||||
<section class="slide" data-title="Market">
|
||||
<span class="section-num">04</span>
|
||||
<p class="num-tag">MARKET</p>
|
||||
<h2 class="h2 mt-s">A very big small business.</h2>
|
||||
<div class="grid g3 mt-l">
|
||||
<div class="metric"><div class="n">73M</div><div class="l">solo businesses in the US + EU</div></div>
|
||||
<div class="metric"><div class="n">$186B</div><div class="l">TAM · horizontal SaaS spend</div></div>
|
||||
<div class="metric"><div class="n">9.4%</div><div class="l">CAGR through 2030</div></div>
|
||||
</div>
|
||||
<p class="lede mt-l">Creators, consultants, indie devs, coaches, freelancers — the fastest-growing segment of the workforce, and the most under-served by tooling.</p>
|
||||
</section>
|
||||
|
||||
<!-- 6. Business model -->
|
||||
<section class="slide" data-title="Business Model">
|
||||
<span class="section-num">05</span>
|
||||
<p class="num-tag">BUSINESS MODEL</p>
|
||||
<h2 class="h2 mt-s">Flat SaaS + payment rake.</h2>
|
||||
<div class="grid g3 mt-l">
|
||||
<div class="card"><h4>Starter</h4><div class="metric mt-s"><div class="n" style="font-size:56px">$29</div><div class="l">/ month · core billing + CRM</div></div></div>
|
||||
<div class="card card-accent"><h4>Pro</h4><div class="metric mt-s"><div class="n" style="font-size:56px">$79</div><div class="l">/ month · contracts, taxes, banking</div></div></div>
|
||||
<div class="card"><h4>+ Payments</h4><div class="metric mt-s"><div class="n" style="font-size:56px">0.4%</div><div class="l">interchange rake on processed volume</div></div></div>
|
||||
</div>
|
||||
<p class="dim mt-l">Blended LTV $1,920 · CAC payback 5 months at current funnel.</p>
|
||||
</section>
|
||||
|
||||
<!-- 7. Traction -->
|
||||
<section class="slide" data-title="Traction">
|
||||
<span class="section-num">06</span>
|
||||
<p class="num-tag">TRACTION</p>
|
||||
<h2 class="h2 mt-s">6 months, growing 38% MoM.</h2>
|
||||
<div class="traction-bar mt-l">
|
||||
<div class="bar" style="height:18%"><em>$6k</em><span>Oct</span></div>
|
||||
<div class="bar" style="height:30%"><em>$11k</em><span>Nov</span></div>
|
||||
<div class="bar" style="height:44%"><em>$17k</em><span>Dec</span></div>
|
||||
<div class="bar" style="height:62%"><em>$26k</em><span>Jan</span></div>
|
||||
<div class="bar" style="height:82%"><em>$38k</em><span>Feb</span></div>
|
||||
<div class="bar" style="height:100%"><em>$54k</em><span>Mar</span></div>
|
||||
</div>
|
||||
<p class="dim mt-l" style="margin-top:48px">2,140 paying customers · NPS 72 · Net retention 118%</p>
|
||||
</section>
|
||||
|
||||
<!-- 8. Team -->
|
||||
<section class="slide" data-title="Team">
|
||||
<span class="section-num">07</span>
|
||||
<p class="num-tag">TEAM</p>
|
||||
<h2 class="h2 mt-s">Shipped at scale before.</h2>
|
||||
<div class="grid g3 mt-l">
|
||||
<div class="card team-card"><div class="avatar">MC</div><h4>Maya Chen</h4><p class="dim">CEO · ex-Stripe billing lead. 8 yrs in payments.</p></div>
|
||||
<div class="card team-card"><div class="avatar">RP</div><h4>Raj Patel</h4><p class="dim">CTO · ex-Linear. Built multiplayer sync at 10M users.</p></div>
|
||||
<div class="card team-card"><div class="avatar">EK</div><h4>Elena Kim</h4><p class="dim">Head of Design · ex-Notion. Shipped the mobile relaunch.</p></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 9. Ask -->
|
||||
<section class="slide" data-title="The Ask">
|
||||
<p class="num-tag">THE ASK</p>
|
||||
<div class="ask-box mt-m">
|
||||
<h2 class="h2">Raising $4.5M seed.</h2>
|
||||
<p class="lede" style="color:rgba(255,255,255,.9);max-width:60ch">18 months of runway to reach $3M ARR. 40% engineering, 35% growth, 15% compliance/banking licenses, 10% runway buffer.</p>
|
||||
<div class="row mt-l" style="gap:40px">
|
||||
<div><div style="font-size:44px;font-weight:900">$4.5M</div><div class="dim">SAFE · post-money cap $28M</div></div>
|
||||
<div><div style="font-size:44px;font-weight:900">18 mo</div><div class="dim">runway to Series A</div></div>
|
||||
<div><div style="font-size:44px;font-weight:900">$3M</div><div class="dim">ARR target by close</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 10. Thanks -->
|
||||
<section class="slide center tc" data-title="Thanks">
|
||||
<div class="cover-bg"></div>
|
||||
<div>
|
||||
<div class="mega">Thanks.</div>
|
||||
<p class="mega-sub">maya@lumen.app · lumen.app/investors</p>
|
||||
<div class="row mt-l" style="justify-content:center;gap:24px">
|
||||
<span class="pill pill-accent">Let's talk</span>
|
||||
<span class="pill">Deck v4.2 · Apr 2026</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
</body></html>
|
||||
79
skills/html-ppt-presenter-mode-reveal/SKILL.md
Normal file
79
skills/html-ppt-presenter-mode-reveal/SKILL.md
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
---
|
||||
name: html-ppt-presenter-mode
|
||||
description: 演讲者模式专用 deck — tokyo-night 默认主题,5 套主题 T 键切换,每页带 150-300 字逐字稿示例(<aside class="notes">),按 S 打开 popup(CURRENT / NEXT / SCRIPT / TIMER 四张磁吸卡片)。用于技术分享、公开演讲、课程讲解,怕忘词或要提词器的场景。
|
||||
triggers:
|
||||
- "presenter mode"
|
||||
- "演讲者模式"
|
||||
- "逐字稿"
|
||||
- "speaker notes"
|
||||
- "提词器"
|
||||
- "presenter view"
|
||||
- "演讲"
|
||||
od:
|
||||
mode: deck
|
||||
scenario: engineering
|
||||
featured: 26
|
||||
upstream: "https://github.com/lewislulu/html-ppt-skill"
|
||||
preview:
|
||||
type: html
|
||||
entry: index.html
|
||||
design_system:
|
||||
requires: false
|
||||
speaker_notes: true
|
||||
animations: true
|
||||
example_prompt: "用 html-ppt-presenter-mode 模板做一份带逐字稿的演讲 PPT。先确认:演讲主题、时长(每页 2-3 分钟)、目标听众。然后帮我每页写 150-300 字的口语化逐字稿(不是讲稿,是提示信号),按 S 能打开 presenter 弹窗。"
|
||||
---
|
||||
# HTML PPT · Presenter Mode (演讲者模式)
|
||||
|
||||
A focused entry point into the [`html-ppt`](../html-ppt/SKILL.md) master skill that lands the user directly on the **`presenter-mode-reveal`** full-deck template.
|
||||
|
||||
## When this card is picked
|
||||
|
||||
The Examples gallery wires "Use this prompt" to the example_prompt above. When you accept that prompt, this card is the right pick if the user wants exactly the visual identity of `presenter-mode-reveal` (see the upstream [full-decks catalog](../html-ppt/references/full-decks.md) for screenshots and rationale).
|
||||
|
||||
## How to author the deck
|
||||
|
||||
1. **Read the master skill first.** All authoring rules live in
|
||||
[`skills/html-ppt/SKILL.md`](../html-ppt/SKILL.md) — content/audience checklist,
|
||||
token rules, layout reuse, presenter mode, the keyboard runtime, and the
|
||||
"never put presenter-only text on the slide" rule.
|
||||
2. **Start from the matching template folder:**
|
||||
`skills/html-ppt/templates/full-decks/presenter-mode-reveal/` — copy `index.html` and
|
||||
`style.css` into the project, keep the `.tpl-presenter-mode-reveal` body class.
|
||||
3. **Bring the shared runtime with the template.** The upstream
|
||||
`index.html` links the shared CSS/JS via `../../../assets/...` because it
|
||||
sits three folders deep inside `skills/html-ppt/templates/full-decks/`.
|
||||
Once you copy `index.html` into the project, those parent-relative URLs
|
||||
no longer resolve and `base.css`, `animations.css`, and `runtime.js`
|
||||
will 404 — meaning the deck never activates and slide navigation is
|
||||
dead. Pick one of these two recipes per project:
|
||||
- **Recipe A — copy + rewrite (preferred):** copy
|
||||
`skills/html-ppt/assets/fonts.css`, `skills/html-ppt/assets/base.css`,
|
||||
`skills/html-ppt/assets/animations/animations.css`, and
|
||||
`skills/html-ppt/assets/runtime.js` into a project-local
|
||||
`assets/` (with `assets/animations/animations.css`), then rewrite the
|
||||
four `<link>`/`<script>` tags in `index.html` from
|
||||
`../../../assets/...` to the matching project-local paths
|
||||
(`assets/fonts.css`, `assets/base.css`,
|
||||
`assets/animations/animations.css`, `assets/runtime.js`).
|
||||
- **Recipe B — inline:** read the same four files and replace each
|
||||
`<link rel="stylesheet" href="../../../assets/...">` with a
|
||||
`<style>...</style>` containing the file's contents, and the
|
||||
`<script src="../../../assets/runtime.js">` with a
|
||||
`<script>...</script>` containing `runtime.js`. Yields a single
|
||||
self-contained `index.html`.
|
||||
Either way, do not ship the upstream `../../../assets/...` URLs
|
||||
verbatim into a project artifact — they only work in-tree.
|
||||
4. **Pick a theme.** Default tokens look fine; if the user wants a different
|
||||
feel, swap in any of the 36 themes from `skills/html-ppt/assets/themes/*.css`
|
||||
via `<link id="theme-link">` and let `T` cycle.
|
||||
5. **Replace demo content, not classes.** The `.tpl-presenter-mode-reveal` scoped CSS only
|
||||
recognises the structural classes shipped in the template — keep them.
|
||||
6. **Speaker notes go inside `<aside class="notes">` or `<div class="notes">`** — never as visible text on the slide.
|
||||
|
||||
## Attribution
|
||||
|
||||
Visual system, layouts, themes and the runtime keyboard model come from
|
||||
the upstream MIT-licensed [`lewislulu/html-ppt-skill`](https://github.com/lewislulu/html-ppt-skill). The
|
||||
LICENSE file ships at `skills/html-ppt/LICENSE`; please keep it in place when
|
||||
redistributing.
|
||||
725
skills/html-ppt-presenter-mode-reveal/example.html
Normal file
725
skills/html-ppt-presenter-mode-reveal/example.html
Normal file
|
|
@ -0,0 +1,725 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh-CN" data-themes="tokyo-night,dracula,catppuccin-mocha,nord,corporate-clean">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>演讲者模式示例 · Presenter Mode Deck</title>
|
||||
<style>/* html-ppt :: shared webfonts */
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@200;300;400;500;600;700;800;900&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@200;300;400;500;600;700;900&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@300;400;600;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,600;0,800;1,400&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@300;400;500;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Archivo+Black&display=swap');
|
||||
|
||||
</style>
|
||||
<style>/* html-ppt :: base.css — reset + shared tokens + layout primitives */
|
||||
/* Default tokens. Themes in assets/themes/*.css override the :root block. */
|
||||
:root {
|
||||
--bg: #ffffff;
|
||||
--bg-soft: #f7f7f8;
|
||||
--surface: #ffffff;
|
||||
--surface-2: #f2f2f4;
|
||||
--border: rgba(0,0,0,.08);
|
||||
--border-strong: rgba(0,0,0,.16);
|
||||
--text-1: #111216;
|
||||
--text-2: #55596a;
|
||||
--text-3: #8a8f9e;
|
||||
--accent: #3b6cff;
|
||||
--accent-2: #7a5cff;
|
||||
--accent-3: #ff5c8a;
|
||||
--good: #1aaf6c;
|
||||
--warn: #f5a524;
|
||||
--bad: #e0445a;
|
||||
--grad: linear-gradient(135deg,#3b6cff,#7a5cff 55%,#ff5c8a);
|
||||
--grad-soft: linear-gradient(135deg,#eef2ff,#f5ecff 55%,#ffeef5);
|
||||
--radius: 18px;
|
||||
--radius-sm: 12px;
|
||||
--radius-lg: 26px;
|
||||
--shadow: 0 10px 30px rgba(18,24,40,.08), 0 2px 6px rgba(18,24,40,.04);
|
||||
--shadow-lg: 0 24px 60px rgba(18,24,40,.14), 0 6px 16px rgba(18,24,40,.06);
|
||||
--font-sans: 'Inter','Noto Sans SC',-apple-system,BlinkMacSystemFont,Helvetica,Arial,sans-serif;
|
||||
--font-serif: 'Playfair Display','Noto Serif SC',Georgia,serif;
|
||||
--font-mono: 'JetBrains Mono','IBM Plex Mono',SFMono-Regular,Menlo,monospace;
|
||||
--font-display: var(--font-sans);
|
||||
--letter-tight: -.03em;
|
||||
--letter-normal: -.01em;
|
||||
--ease: cubic-bezier(.4,0,.2,1);
|
||||
}
|
||||
|
||||
*,*::before,*::after{box-sizing:border-box}
|
||||
html,body{margin:0;padding:0;background:var(--bg);color:var(--text-1);
|
||||
font-family:var(--font-sans);font-weight:400;line-height:1.6;
|
||||
-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;
|
||||
letter-spacing:var(--letter-normal)}
|
||||
img,svg,video{max-width:100%;display:block}
|
||||
a{color:var(--accent);text-decoration:none}
|
||||
a:hover{text-decoration:underline}
|
||||
code,kbd,pre,samp{font-family:var(--font-mono)}
|
||||
|
||||
/* ================= SLIDE SYSTEM ================= */
|
||||
.deck{position:relative;width:100vw;height:100vh;overflow:hidden;background:var(--bg)}
|
||||
.slide{
|
||||
position:absolute;inset:0;
|
||||
display:flex;flex-direction:column;justify-content:center;
|
||||
padding:72px 96px;
|
||||
box-sizing:border-box;
|
||||
opacity:0;pointer-events:none;
|
||||
transition:opacity .5s var(--ease), transform .5s var(--ease);
|
||||
transform:translateX(30px);
|
||||
overflow:hidden;
|
||||
}
|
||||
.slide.is-active{opacity:1;pointer-events:auto;transform:translateX(0);z-index:2}
|
||||
.slide.is-prev{transform:translateX(-30px)}
|
||||
|
||||
/* single-page standalone (used when a layout file is opened directly) */
|
||||
body.single .slide{position:relative;width:100vw;height:100vh;opacity:1;transform:none;pointer-events:auto}
|
||||
|
||||
/* ================= TYPOGRAPHY ================= */
|
||||
.eyebrow{font-size:13px;font-weight:500;letter-spacing:.16em;text-transform:uppercase;color:var(--text-3)}
|
||||
.kicker{font-size:14px;font-weight:600;color:var(--accent);letter-spacing:.08em;text-transform:uppercase}
|
||||
h1.title,.h1{font-family:var(--font-display);font-size:72px;line-height:1.05;font-weight:800;letter-spacing:var(--letter-tight);margin:0 0 18px;color:var(--text-1)}
|
||||
h2.title,.h2{font-family:var(--font-display);font-size:54px;line-height:1.1;font-weight:700;letter-spacing:var(--letter-tight);margin:0 0 14px}
|
||||
h3,.h3{font-size:32px;line-height:1.2;font-weight:600;letter-spacing:var(--letter-normal);margin:0 0 10px}
|
||||
h4,.h4{font-size:22px;line-height:1.3;font-weight:600;margin:0 0 8px}
|
||||
.lede{font-size:22px;line-height:1.55;color:var(--text-2);font-weight:300;max-width:62ch}
|
||||
.dim{color:var(--text-2)}
|
||||
.dim2{color:var(--text-3)}
|
||||
.mono{font-family:var(--font-mono)}
|
||||
.serif{font-family:var(--font-serif)}
|
||||
.gradient-text{background:var(--grad);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;color:transparent}
|
||||
|
||||
/* ================= LAYOUT PRIMITIVES ================= */
|
||||
.stack>*+*{margin-top:14px}
|
||||
.row{display:flex;gap:24px;align-items:center}
|
||||
.row.wrap{flex-wrap:wrap}
|
||||
.grid{display:grid;gap:24px}
|
||||
.g2{grid-template-columns:repeat(2,1fr)}
|
||||
.g3{grid-template-columns:repeat(3,1fr)}
|
||||
.g4{grid-template-columns:repeat(4,1fr)}
|
||||
.center{display:flex;align-items:center;justify-content:center;text-align:center}
|
||||
.fill{flex:1}
|
||||
.sp-t{padding-top:24px}.sp-b{padding-bottom:24px}
|
||||
.mt-s{margin-top:8px}.mt-m{margin-top:18px}.mt-l{margin-top:32px}
|
||||
.mb-s{margin-bottom:8px}.mb-m{margin-bottom:18px}.mb-l{margin-bottom:32px}
|
||||
|
||||
/* ================= CARDS ================= */
|
||||
.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);
|
||||
padding:26px 28px;box-shadow:var(--shadow);position:relative;overflow:hidden}
|
||||
.card-soft{background:var(--surface-2);border:1px solid var(--border)}
|
||||
.card-outline{background:transparent;border:1.5px solid var(--border-strong);box-shadow:none}
|
||||
.card-accent{background:var(--surface);border-top:3px solid var(--accent)}
|
||||
.card-hover{transition:transform .3s var(--ease),box-shadow .3s var(--ease)}
|
||||
.card-hover:hover{transform:translateY(-4px);box-shadow:var(--shadow-lg)}
|
||||
|
||||
.pill{display:inline-block;padding:4px 12px;border-radius:999px;font-size:12px;font-weight:500;
|
||||
background:var(--surface-2);color:var(--text-2);border:1px solid var(--border)}
|
||||
.pill-accent{background:color-mix(in srgb,var(--accent) 12%,transparent);color:var(--accent);border-color:color-mix(in srgb,var(--accent) 28%,transparent)}
|
||||
|
||||
/* ================= BARS / DIVIDERS ================= */
|
||||
.divider{height:1px;background:var(--border);width:100%}
|
||||
.divider-accent{height:3px;width:72px;background:var(--accent);border-radius:2px}
|
||||
|
||||
/* ================= CHROME (header/footer/progress) ================= */
|
||||
.deck-header{position:absolute;top:24px;left:40px;right:40px;display:flex;align-items:center;justify-content:space-between;
|
||||
font-size:12px;color:var(--text-3);letter-spacing:.12em;text-transform:uppercase;z-index:10;pointer-events:none}
|
||||
.deck-footer{position:absolute;bottom:24px;left:40px;right:40px;display:flex;align-items:center;justify-content:space-between;
|
||||
font-size:12px;color:var(--text-3);z-index:10;pointer-events:none}
|
||||
.slide-number::before{content:attr(data-current)}
|
||||
.slide-number::after{content:" / " attr(data-total)}
|
||||
.progress-bar{position:fixed;left:0;right:0;bottom:0;height:3px;background:transparent;z-index:20}
|
||||
.progress-bar > span{display:block;height:100%;width:0;background:var(--accent);transition:width .3s var(--ease)}
|
||||
|
||||
/* ================= PRESENTER / OVERVIEW ================= */
|
||||
.notes{display:none!important}
|
||||
.notes-overlay{position:fixed;inset:auto 0 0 0;max-height:42vh;background:rgba(20,22,30,.95);color:#e8ebf4;
|
||||
padding:20px 32px;font-size:16px;line-height:1.6;border-top:1px solid rgba(255,255,255,.1);transform:translateY(100%);
|
||||
transition:transform .3s var(--ease);z-index:40;overflow:auto;font-family:var(--font-sans)}
|
||||
.notes-overlay.open{transform:translateY(0)}
|
||||
.overview{position:fixed;inset:0;background:rgba(10,12,18,.92);backdrop-filter:blur(12px);z-index:50;
|
||||
display:none;padding:40px;overflow:auto}
|
||||
.overview.open{display:grid;grid-template-columns:repeat(4,1fr);gap:20px;align-content:start}
|
||||
.overview .thumb{background:var(--surface);border:1px solid var(--border);border-radius:12px;
|
||||
aspect-ratio:16/9;overflow:hidden;cursor:pointer;position:relative;color:var(--text-1);padding:16px;
|
||||
font-size:11px;transition:transform .2s var(--ease)}
|
||||
.overview .thumb:hover{transform:scale(1.04)}
|
||||
.overview .thumb .n{position:absolute;top:8px;left:10px;font-weight:700;font-size:14px;color:var(--text-3)}
|
||||
.overview .thumb .t{position:absolute;bottom:10px;left:14px;right:14px;font-weight:600;color:var(--text-1)}
|
||||
|
||||
/* ================= PRESENTER VIEW ================= */
|
||||
/* Presenter view opens in a separate popup window (S key).
|
||||
* All presenter styles are self-contained in the popup HTML generated by runtime.js.
|
||||
* The audience window (this file) is NOT affected — it stays as normal deck view.
|
||||
* Only the .notes class below is needed to hide speaker notes from audience. */
|
||||
|
||||
/* ================= UTILITY ================= */
|
||||
.hidden{display:none!important}
|
||||
.nowrap{white-space:nowrap}
|
||||
.tr{text-align:right}.tc{text-align:center}.tl{text-align:left}
|
||||
.uppercase{text-transform:uppercase;letter-spacing:.12em}
|
||||
|
||||
/* ================= PRINT ================= */
|
||||
@media print{
|
||||
.slide{position:relative;opacity:1!important;transform:none!important;page-break-after:always;height:100vh}
|
||||
.deck-header,.deck-footer,.progress-bar,.notes-overlay,.overview{display:none!important}
|
||||
}
|
||||
|
||||
</style>
|
||||
<style data-theme="tokyo-night">/* theme: tokyo-night */
|
||||
:root{
|
||||
--bg:#1a1b26;--bg-soft:#16161e;--surface:#24283b;--surface-2:#2f334d;
|
||||
--border:rgba(192,202,245,.12);--border-strong:rgba(192,202,245,.24);
|
||||
--text-1:#c0caf5;--text-2:#a9b1d6;--text-3:#565f89;
|
||||
--accent:#7aa2f7;--accent-2:#bb9af7;--accent-3:#7dcfff;
|
||||
--good:#9ece6a;--warn:#e0af68;--bad:#f7768e;
|
||||
--grad:linear-gradient(135deg,#7aa2f7,#bb9af7 55%,#f7768e);
|
||||
--grad-soft:linear-gradient(135deg,#24283b,#2f334d);
|
||||
--radius:12px;--radius-sm:8px;--radius-lg:20px;
|
||||
--shadow:0 10px 30px rgba(0,0,0,.45);
|
||||
--shadow-lg:0 24px 62px rgba(0,0,0,.6);
|
||||
--font-sans:'Inter','Noto Sans SC',sans-serif;
|
||||
}
|
||||
|
||||
</style>
|
||||
<style>/* html-ppt :: animations.css
|
||||
* Apply by adding class="anim-<name>" or data-anim="<name>".
|
||||
* Durations are deliberately snappy; tweak --anim-dur per element.
|
||||
*/
|
||||
:root{--anim-dur:.7s;--anim-ease:cubic-bezier(.4,0,.2,1)}
|
||||
|
||||
/* ---------- FADE DIRECTIONALS ---------- */
|
||||
@keyframes kf-fade-up{from{opacity:0;transform:translateY(32px)}to{opacity:1;transform:none}}
|
||||
@keyframes kf-fade-down{from{opacity:0;transform:translateY(-32px)}to{opacity:1;transform:none}}
|
||||
@keyframes kf-fade-left{from{opacity:0;transform:translateX(-40px)}to{opacity:1;transform:none}}
|
||||
@keyframes kf-fade-right{from{opacity:0;transform:translateX(40px)}to{opacity:1;transform:none}}
|
||||
.anim-fade-up{animation:kf-fade-up var(--anim-dur) var(--anim-ease) both}
|
||||
.anim-fade-down{animation:kf-fade-down var(--anim-dur) var(--anim-ease) both}
|
||||
.anim-fade-left{animation:kf-fade-left var(--anim-dur) var(--anim-ease) both}
|
||||
.anim-fade-right{animation:kf-fade-right var(--anim-dur) var(--anim-ease) both}
|
||||
|
||||
/* ---------- RISE / DROP / ZOOM / BLUR / GLITCH ---------- */
|
||||
@keyframes kf-rise{from{opacity:0;transform:translateY(60px) scale(.97);filter:blur(6px)}to{opacity:1;transform:none;filter:none}}
|
||||
@keyframes kf-drop{from{opacity:0;transform:translateY(-60px) scale(.97)}to{opacity:1;transform:none}}
|
||||
@keyframes kf-zoom{0%{opacity:0;transform:scale(.6)}60%{transform:scale(1.04)}100%{opacity:1;transform:scale(1)}}
|
||||
@keyframes kf-blur{from{opacity:0;filter:blur(18px)}to{opacity:1;filter:none}}
|
||||
@keyframes kf-glitch{0%{opacity:0;transform:translateX(0);clip-path:inset(0 0 0 0)}
|
||||
20%{opacity:1;transform:translateX(-6px);clip-path:inset(20% 0 30% 0)}
|
||||
40%{transform:translateX(4px);clip-path:inset(50% 0 10% 0)}
|
||||
60%{transform:translateX(-3px);clip-path:inset(10% 0 60% 0)}
|
||||
80%{transform:translateX(2px);clip-path:inset(0 0 0 0)}
|
||||
100%{opacity:1;transform:none}}
|
||||
.anim-rise-in{animation:kf-rise .9s var(--anim-ease) both}
|
||||
.anim-drop-in{animation:kf-drop .8s var(--anim-ease) both}
|
||||
.anim-zoom-pop{animation:kf-zoom .7s cubic-bezier(.22,1.3,.36,1) both}
|
||||
.anim-blur-in{animation:kf-blur .8s var(--anim-ease) both}
|
||||
.anim-glitch-in{animation:kf-glitch .8s steps(5,end) both}
|
||||
|
||||
/* ---------- TYPEWRITER ---------- */
|
||||
.anim-typewriter{display:inline-block;overflow:hidden;white-space:nowrap;border-right:2px solid currentColor;
|
||||
width:0;animation:kf-type 2.4s steps(40,end) forwards, kf-caret 1s step-end infinite}
|
||||
@keyframes kf-type{to{width:100%}}
|
||||
@keyframes kf-caret{50%{border-color:transparent}}
|
||||
|
||||
/* ---------- GLOW / SHIMMER / GRADIENT-FLOW ---------- */
|
||||
@keyframes kf-neon{0%,100%{text-shadow:0 0 8px var(--accent),0 0 20px var(--accent)}
|
||||
50%{text-shadow:0 0 16px var(--accent),0 0 40px var(--accent),0 0 80px var(--accent)}}
|
||||
.anim-neon-glow{animation:kf-neon 2s ease-in-out infinite}
|
||||
|
||||
.anim-shimmer-sweep{position:relative;overflow:hidden}
|
||||
.anim-shimmer-sweep::after{content:"";position:absolute;inset:0;
|
||||
background:linear-gradient(110deg,transparent 40%,rgba(255,255,255,.55) 50%,transparent 60%);
|
||||
transform:translateX(-100%);animation:kf-shimmer 2.4s var(--anim-ease) infinite}
|
||||
@keyframes kf-shimmer{to{transform:translateX(100%)}}
|
||||
|
||||
.anim-gradient-flow{background:linear-gradient(90deg,var(--accent),var(--accent-2,var(--accent)),var(--accent-3,var(--accent)),var(--accent));
|
||||
background-size:300% 100%;-webkit-background-clip:text;background-clip:text;color:transparent;-webkit-text-fill-color:transparent;
|
||||
animation:kf-gradflow 4s linear infinite}
|
||||
@keyframes kf-gradflow{to{background-position:300% 0}}
|
||||
|
||||
/* ---------- STAGGER LIST ---------- */
|
||||
.anim-stagger-list > *{opacity:0;animation:kf-rise .65s var(--anim-ease) both}
|
||||
.anim-stagger-list > *:nth-child(1){animation-delay:.05s}
|
||||
.anim-stagger-list > *:nth-child(2){animation-delay:.15s}
|
||||
.anim-stagger-list > *:nth-child(3){animation-delay:.25s}
|
||||
.anim-stagger-list > *:nth-child(4){animation-delay:.35s}
|
||||
.anim-stagger-list > *:nth-child(5){animation-delay:.45s}
|
||||
.anim-stagger-list > *:nth-child(6){animation-delay:.55s}
|
||||
.anim-stagger-list > *:nth-child(7){animation-delay:.65s}
|
||||
.anim-stagger-list > *:nth-child(8){animation-delay:.75s}
|
||||
.anim-stagger-list > *:nth-child(n+9){animation-delay:.85s}
|
||||
|
||||
/* ---------- COUNTER-UP (JS-driven, marker class only) ---------- */
|
||||
.counter{font-variant-numeric:tabular-nums}
|
||||
|
||||
/* ---------- SVG PATH DRAW ---------- */
|
||||
.anim-path-draw path,.anim-path-draw line,.anim-path-draw polyline,.anim-path-draw circle,.anim-path-draw rect{
|
||||
stroke-dasharray:1000;stroke-dashoffset:1000;animation:kf-draw 2s var(--anim-ease) forwards}
|
||||
@keyframes kf-draw{to{stroke-dashoffset:0}}
|
||||
|
||||
/* ---------- PARALLAX TILT (hover) ---------- */
|
||||
.anim-parallax-tilt{transform-style:preserve-3d;transition:transform .4s var(--anim-ease)}
|
||||
.anim-parallax-tilt:hover{transform:perspective(900px) rotateX(6deg) rotateY(-8deg) translateZ(10px)}
|
||||
|
||||
/* ---------- CARD FLIP 3D ---------- */
|
||||
@keyframes kf-flip{from{transform:perspective(1200px) rotateY(-90deg);opacity:0}
|
||||
to{transform:perspective(1200px) rotateY(0);opacity:1}}
|
||||
.anim-card-flip-3d{animation:kf-flip .9s var(--anim-ease) both;transform-style:preserve-3d;backface-visibility:hidden}
|
||||
|
||||
/* ---------- CUBE ROTATE 3D ---------- */
|
||||
@keyframes kf-cube{from{transform:perspective(1200px) rotateX(20deg) rotateY(-90deg) translateZ(-200px);opacity:0}
|
||||
to{transform:perspective(1200px) rotateX(0) rotateY(0) translateZ(0);opacity:1}}
|
||||
.anim-cube-rotate-3d{animation:kf-cube 1s var(--anim-ease) both}
|
||||
|
||||
/* ---------- PAGE TURN 3D ---------- */
|
||||
@keyframes kf-pageturn{from{transform:perspective(1600px) rotateY(-85deg);transform-origin:left center;opacity:0}
|
||||
to{transform:perspective(1600px) rotateY(0);opacity:1}}
|
||||
.anim-page-turn-3d{animation:kf-pageturn 1s var(--anim-ease) both;transform-origin:left center}
|
||||
|
||||
/* ---------- PERSPECTIVE ZOOM ---------- */
|
||||
@keyframes kf-pzoom{from{opacity:0;transform:perspective(1400px) translateZ(-400px) rotateX(12deg)}
|
||||
to{opacity:1;transform:none}}
|
||||
.anim-perspective-zoom{animation:kf-pzoom 1s var(--anim-ease) both}
|
||||
|
||||
/* ---------- MARQUEE SCROLL ---------- */
|
||||
.anim-marquee-scroll{display:flex;gap:48px;white-space:nowrap;animation:kf-marquee 20s linear infinite}
|
||||
@keyframes kf-marquee{from{transform:translateX(0)}to{transform:translateX(-50%)}}
|
||||
|
||||
/* ---------- KEN BURNS ---------- */
|
||||
@keyframes kf-kenburns{0%{transform:scale(1) translate(0,0)}100%{transform:scale(1.15) translate(-2%,-1%)}}
|
||||
.anim-kenburns{animation:kf-kenburns 14s ease-in-out infinite alternate}
|
||||
|
||||
/* ---------- CONFETTI BURST (pseudo — pure CSS sparkles) ---------- */
|
||||
.anim-confetti-burst{position:relative}
|
||||
.anim-confetti-burst::before,.anim-confetti-burst::after{
|
||||
content:"";position:absolute;top:50%;left:50%;width:8px;height:8px;border-radius:50%;
|
||||
background:var(--accent);box-shadow:
|
||||
20px -30px 0 var(--accent-2,var(--accent)),-25px -20px 0 var(--accent-3,var(--accent)),
|
||||
30px 20px 0 var(--good,#1aaf6c),-30px 25px 0 var(--warn,#f5a524),
|
||||
40px -10px 0 var(--bad,#e0445a),-45px 0 0 var(--accent),
|
||||
10px 40px 0 var(--accent-2,var(--accent)),-15px -40px 0 var(--accent-3,var(--accent));
|
||||
opacity:0;animation:kf-confetti 1.2s var(--anim-ease) forwards}
|
||||
.anim-confetti-burst::after{animation-delay:.15s;transform:rotate(45deg)}
|
||||
@keyframes kf-confetti{0%{opacity:0;transform:scale(.2)}30%{opacity:1}100%{opacity:0;transform:scale(2.2)}}
|
||||
|
||||
/* ---------- SPOTLIGHT ---------- */
|
||||
@keyframes kf-spot{0%{clip-path:circle(0% at 50% 50%)}100%{clip-path:circle(140% at 50% 50%)}}
|
||||
.anim-spotlight{animation:kf-spot 1.1s var(--anim-ease) both}
|
||||
|
||||
/* ---------- MORPH SHAPE (SVG) ---------- */
|
||||
.anim-morph-shape path{animation:kf-morph 6s ease-in-out infinite alternate}
|
||||
@keyframes kf-morph{0%{d:path("M60,120 Q120,20 180,120 T300,120")}
|
||||
100%{d:path("M60,120 Q120,220 180,120 T300,120")}}
|
||||
|
||||
/* ---------- RIPPLE REVEAL ---------- */
|
||||
@keyframes kf-ripple{0%{clip-path:circle(0% at 20% 80%);opacity:.4}
|
||||
100%{clip-path:circle(160% at 20% 80%);opacity:1}}
|
||||
.anim-ripple-reveal{animation:kf-ripple 1.2s var(--anim-ease) both}
|
||||
|
||||
/* reduced motion */
|
||||
@media (prefers-reduced-motion: reduce){
|
||||
[class*="anim-"]{animation:none!important;transition:none!important}
|
||||
}
|
||||
|
||||
</style>
|
||||
<style>/* tpl-presenter-mode-reveal · scoped styles
|
||||
* Presenter-mode demo deck. Inherits tokens from active theme.
|
||||
* Minimal overrides — focus is on content + notes structure.
|
||||
*/
|
||||
|
||||
.tpl-presenter-mode-reveal .slide {
|
||||
padding: 72px 96px;
|
||||
}
|
||||
|
||||
.tpl-presenter-mode-reveal .kicker {
|
||||
font-family: var(--font-mono, monospace);
|
||||
font-size: 13px;
|
||||
color: var(--text-3);
|
||||
letter-spacing: 0.14em;
|
||||
text-transform: uppercase;
|
||||
margin: 0 0 18px 0;
|
||||
}
|
||||
|
||||
.tpl-presenter-mode-reveal .h1 {
|
||||
font-size: clamp(44px, 5.6vw, 76px);
|
||||
line-height: 1.12;
|
||||
letter-spacing: -0.02em;
|
||||
margin: 0 0 24px 0;
|
||||
}
|
||||
|
||||
.tpl-presenter-mode-reveal .h2 {
|
||||
font-size: clamp(32px, 3.6vw, 48px);
|
||||
line-height: 1.22;
|
||||
letter-spacing: -0.01em;
|
||||
margin: 0 0 28px 0;
|
||||
}
|
||||
|
||||
.tpl-presenter-mode-reveal .lede {
|
||||
font-size: 20px;
|
||||
line-height: 1.55;
|
||||
color: var(--text-2);
|
||||
}
|
||||
|
||||
.tpl-presenter-mode-reveal .mono {
|
||||
font-family: var(--font-mono, monospace);
|
||||
font-size: 0.9em;
|
||||
padding: 2px 8px;
|
||||
border-radius: 6px;
|
||||
background: rgba(255,255,255,0.08);
|
||||
color: var(--accent, #58a6ff);
|
||||
}
|
||||
|
||||
.tpl-presenter-mode-reveal .accent {
|
||||
color: var(--accent, #f0883e);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.tpl-presenter-mode-reveal .speaker {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-top: 32px;
|
||||
}
|
||||
.tpl-presenter-mode-reveal .speaker .av {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, var(--accent, #58a6ff), #bc8cff);
|
||||
}
|
||||
.tpl-presenter-mode-reveal .speaker b {
|
||||
display: block;
|
||||
font-size: 16px;
|
||||
}
|
||||
.tpl-presenter-mode-reveal .speaker span {
|
||||
font-size: 13px;
|
||||
color: var(--text-3);
|
||||
}
|
||||
|
||||
/* Agenda rows */
|
||||
.tpl-presenter-mode-reveal .agenda-row {
|
||||
display: grid;
|
||||
grid-template-columns: 48px 1fr auto;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
padding: 14px 18px;
|
||||
border: 1px solid var(--border, rgba(255,255,255,0.1));
|
||||
border-radius: 10px;
|
||||
margin-bottom: 10px;
|
||||
background: var(--surface, rgba(255,255,255,0.03));
|
||||
}
|
||||
.tpl-presenter-mode-reveal .agenda-row .num {
|
||||
font-family: var(--font-mono, monospace);
|
||||
font-size: 14px;
|
||||
color: var(--accent, #58a6ff);
|
||||
font-weight: 700;
|
||||
}
|
||||
.tpl-presenter-mode-reveal .agenda-row .t {
|
||||
font-size: 17px;
|
||||
font-weight: 500;
|
||||
color: var(--text-1);
|
||||
}
|
||||
.tpl-presenter-mode-reveal .agenda-row .d {
|
||||
font-family: var(--font-mono, monospace);
|
||||
font-size: 12px;
|
||||
color: var(--text-3);
|
||||
}
|
||||
|
||||
/* Cards */
|
||||
.tpl-presenter-mode-reveal .card {
|
||||
background: var(--surface, rgba(255,255,255,0.03));
|
||||
border: 1px solid var(--border, rgba(255,255,255,0.1));
|
||||
border-radius: 12px;
|
||||
padding: 22px 24px;
|
||||
}
|
||||
.tpl-presenter-mode-reveal .card-accent {
|
||||
border-top: 3px solid var(--accent, #58a6ff);
|
||||
}
|
||||
.tpl-presenter-mode-reveal .card h4 {
|
||||
margin: 0 0 10px 0;
|
||||
font-size: 18px;
|
||||
color: var(--text-1);
|
||||
}
|
||||
.tpl-presenter-mode-reveal .card .dim {
|
||||
color: var(--text-2);
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Feature rows (presenter view features) */
|
||||
.tpl-presenter-mode-reveal .feature-row {
|
||||
display: flex;
|
||||
gap: 14px;
|
||||
padding: 14px 0;
|
||||
border-bottom: 1px solid var(--border, rgba(255,255,255,0.08));
|
||||
}
|
||||
.tpl-presenter-mode-reveal .feature-row:last-child { border-bottom: none; }
|
||||
.tpl-presenter-mode-reveal .feature-row .num {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.tpl-presenter-mode-reveal .feature-row b {
|
||||
display: block;
|
||||
font-size: 17px;
|
||||
margin-bottom: 4px;
|
||||
color: var(--text-1);
|
||||
}
|
||||
.tpl-presenter-mode-reveal .feature-row .dim {
|
||||
font-size: 14px;
|
||||
color: var(--text-2);
|
||||
line-height: 1.55;
|
||||
margin: 0;
|
||||
}
|
||||
.tpl-presenter-mode-reveal .blue { color: #58a6ff; }
|
||||
.tpl-presenter-mode-reveal .green { color: #3fb950; }
|
||||
.tpl-presenter-mode-reveal .orange { color: #f0883e; }
|
||||
.tpl-presenter-mode-reveal .purple { color: #bc8cff; }
|
||||
.tpl-presenter-mode-reveal .red { color: #f85149; }
|
||||
|
||||
/* Rule rows (3 铁律) */
|
||||
.tpl-presenter-mode-reveal .rule-row {
|
||||
display: grid;
|
||||
grid-template-columns: 56px 1fr;
|
||||
gap: 20px;
|
||||
align-items: start;
|
||||
padding: 18px 22px;
|
||||
border: 1px solid var(--border, rgba(255,255,255,0.1));
|
||||
border-radius: 12px;
|
||||
margin-bottom: 14px;
|
||||
background: var(--surface, rgba(255,255,255,0.03));
|
||||
}
|
||||
.tpl-presenter-mode-reveal .rule-row .num {
|
||||
font-size: 28px;
|
||||
font-weight: 800;
|
||||
font-family: var(--font-mono, monospace);
|
||||
line-height: 1;
|
||||
}
|
||||
.tpl-presenter-mode-reveal .rule-row b {
|
||||
display: block;
|
||||
font-size: 18px;
|
||||
margin-bottom: 6px;
|
||||
color: var(--text-1);
|
||||
}
|
||||
.tpl-presenter-mode-reveal .rule-row .dim {
|
||||
font-size: 15px;
|
||||
color: var(--text-2);
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Code block */
|
||||
.tpl-presenter-mode-reveal .code-block {
|
||||
background: #0d1117;
|
||||
border: 1px solid rgba(255,255,255,0.1);
|
||||
border-radius: 12px;
|
||||
padding: 20px 26px;
|
||||
font-family: var(--font-mono, "SF Mono", monospace);
|
||||
font-size: 15px;
|
||||
line-height: 1.8;
|
||||
color: #e6edf3;
|
||||
white-space: pre-wrap;
|
||||
text-align: left;
|
||||
}
|
||||
.tpl-presenter-mode-reveal .code-block .comment { color: #8b949e; }
|
||||
.tpl-presenter-mode-reveal .code-block .cmd { color: #3fb950; font-weight: 600; }
|
||||
.tpl-presenter-mode-reveal .code-block .flag { color: #f0883e; }
|
||||
|
||||
/* Stack helper */
|
||||
.tpl-presenter-mode-reveal .stack > * + * { margin-top: 0; }
|
||||
|
||||
/* Grid helpers */
|
||||
.tpl-presenter-mode-reveal .grid { display: grid; gap: 20px; }
|
||||
.tpl-presenter-mode-reveal .grid.g2 { grid-template-columns: 1fr 1fr; }
|
||||
.tpl-presenter-mode-reveal .grid.g3 { grid-template-columns: repeat(3, 1fr); }
|
||||
|
||||
.tpl-presenter-mode-reveal .mt-m { margin-top: 20px; }
|
||||
.tpl-presenter-mode-reveal .mt-l { margin-top: 32px; }
|
||||
.tpl-presenter-mode-reveal .mt-s { margin-top: 10px; }
|
||||
.tpl-presenter-mode-reveal .tc { text-align: center; }
|
||||
|
||||
</style>
|
||||
<style>
|
||||
/* Static-preview fallback (runtime.js is absent — keep every slide visible) */
|
||||
.deck{height:auto;min-height:100vh;overflow:visible}
|
||||
.slide{position:relative;inset:auto;opacity:1;pointer-events:auto;transform:none;height:100vh;page-break-after:always}
|
||||
.deck-header,.deck-footer,.slide-number,.progress-bar,.notes-overlay,.overview{pointer-events:none}
|
||||
.notes{display:none!important}
|
||||
</style></head>
|
||||
<body class="tpl-presenter-mode-reveal">
|
||||
<div class="deck">
|
||||
|
||||
<!-- ============ 1. COVER ============ -->
|
||||
<section class="slide" data-title="Cover">
|
||||
<p class="kicker">presenter-mode / demo</p>
|
||||
<h1 class="h1 anim-fade-up" data-anim="fade-up">如何做一场<br><span style="background:var(--grad);-webkit-background-clip:text;background-clip:text;color:transparent">有逐字稿</span>的技术分享</h1>
|
||||
<p class="lede mt-m">按 <span class="mono">S</span> 进入演讲者视图 · <span class="mono">T</span> 切换主题 · <span class="mono">← →</span> 翻页</p>
|
||||
<div class="speaker">
|
||||
<div class="av"></div>
|
||||
<div><b>@lewis</b><span>sharing talk · 30 min</span></div>
|
||||
</div>
|
||||
<div class="deck-footer">
|
||||
<span class="mono">#presenter #逐字稿 #tech-talk</span>
|
||||
<span class="slide-number" data-current="1" data-total="6"></span>
|
||||
</div>
|
||||
<aside class="notes">
|
||||
<p>大家好,欢迎来到今天的技术分享。我是 lewis,今天想跟大家聊一个很多人忽略但其实非常影响演讲效果的话题——<strong>如何让一场技术分享既有深度,又讲得不卡壳</strong>。</p>
|
||||
<p>在正式开始之前,先跟大家介绍一下这份 deck 本身:这是一个支持<em>演讲者模式</em>的 HTML 幻灯片模板。现在你们看到的是观众视图,但我自己的屏幕上看到的是完全不一样的东西——当前页、下一页、完整逐字稿、计时器,全在一块屏幕上。</p>
|
||||
<p>为什么我要专门做这个?因为我发现自己做技术分享时最大的痛点不是 PPT 不够好看,而是<strong>讲到某一页突然不知道该说什么,或者忘了过渡怎么接</strong>。今天这份分享既是内容本身,也是个演示——我会一直开着演讲者模式讲,你们可以观察我讲得有多流畅。</p>
|
||||
<p>今天分享大概 30 分钟,分 5 个部分。有问题随时打断。Let's go.</p>
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
<!-- ============ 2. AGENDA ============ -->
|
||||
<section class="slide" data-title="Agenda">
|
||||
<p class="kicker">agenda</p>
|
||||
<h2 class="h2">今天要讲的 5 件事</h2>
|
||||
<div class="stack mt-l">
|
||||
<div class="agenda-row"><span class="num">01</span><span class="t">为什么 PPT 本身做得好还不够</span><span class="d">~5min</span></div>
|
||||
<div class="agenda-row"><span class="num">02</span><span class="t">演讲者模式到底该有哪些信息</span><span class="d">~6min</span></div>
|
||||
<div class="agenda-row"><span class="num">03</span><span class="t">逐字稿怎么写才不像念稿</span><span class="d">~8min</span></div>
|
||||
<div class="agenda-row"><span class="num">04</span><span class="t">Live demo · html-ppt skill 怎么用</span><span class="d">~8min</span></div>
|
||||
<div class="agenda-row"><span class="num">05</span><span class="t">Takeaways + Q&A</span><span class="d">~3min</span></div>
|
||||
</div>
|
||||
<aside class="notes">
|
||||
<p>先过一下今天的议程。</p>
|
||||
<p>第一部分我想先说服你们<strong>"PPT 做得漂亮≠讲得好"</strong>。我见过太多很精致的 deck,但讲的人一上去就开始 "嗯…这个…就是…"。</p>
|
||||
<p>第二部分聊演讲者视图。业界的产品其实差别蛮大的,Keynote、PowerPoint、reveal.js 都有各自的方案,但真正好用的设计逻辑是什么,我会给出我的答案。</p>
|
||||
<p>第三部分是今天的<em>核心</em>——逐字稿。很多人以为逐字稿就是把要说的话一字不差写下来,错。逐字稿的目的是让你<strong>"看一眼就接得上"</strong>,写法完全不一样。</p>
|
||||
<p>第四部分会现场 demo 我自己用的 html-ppt skill,展示如何 30 分钟出一份带逐字稿的 deck。</p>
|
||||
<p>最后收尾 + 答疑。</p>
|
||||
<p>OK,进入第一部分。</p>
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
<!-- ============ 3. PROBLEM ============ -->
|
||||
<section class="slide" data-title="Problem">
|
||||
<p class="kicker">// part 01 · problem</p>
|
||||
<h2 class="h2">做 PPT 和讲 PPT,<br>是<span class="accent">两件事</span>。</h2>
|
||||
<div class="grid g3 mt-l">
|
||||
<div class="card card-accent">
|
||||
<h4>✅ PPT 做得好</h4>
|
||||
<p class="dim">主题统一、排版干净、图表清晰、动效克制。这些是"静态作品"的质量。</p>
|
||||
</div>
|
||||
<div class="card card-accent">
|
||||
<h4>❌ 讲得好</h4>
|
||||
<p class="dim">逻辑连贯、语速稳定、不 "嗯啊"、能接住问题、能当场调整节奏。</p>
|
||||
</div>
|
||||
<div class="card card-accent">
|
||||
<h4>💡 差别在哪</h4>
|
||||
<p class="dim">前者是<strong>纸上功夫</strong>,后者需要你<strong>"看一眼幻灯片就知道下句话说什么"</strong>。</p>
|
||||
</div>
|
||||
</div>
|
||||
<aside class="notes">
|
||||
<p>我先抛一个可能有争议的观点——<strong>做 PPT 和讲 PPT 是两件完全不同的事</strong>。</p>
|
||||
<p>大家看左边这张卡片,"PPT 做得好" 意味着什么?主题统一、排版干净、图表清晰、动效克制——这些都是<em>静态作品</em>的质量标准,可以离线评判。</p>
|
||||
<p>但中间这张卡片就不一样了:"讲得好" 意味着逻辑连贯、语速稳定、不卡壳、能接住提问、能根据现场反应调整节奏——这些是<strong>临场能力</strong>,跟 PPT 好不好看基本没关系。</p>
|
||||
<p>最关键的是右边这句话——讲得好的人,本质上是"<strong>看一眼幻灯片就知道下句话说什么</strong>"。这个能力靠什么?不是背稿,也不是即兴发挥,而是靠<em>合理设计的提词器系统</em>。</p>
|
||||
<p>今天接下来 25 分钟,我就是围绕这个核心问题展开的。</p>
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
<!-- ============ 4. SOLUTION ============ -->
|
||||
<section class="slide" data-title="Presenter View">
|
||||
<p class="kicker">// part 02 · presenter view</p>
|
||||
<h2 class="h2">演讲者视图应该有<span class="accent">四块信息</span></h2>
|
||||
<div class="grid g2 mt-l">
|
||||
<div>
|
||||
<div class="feature-row"><span class="num blue">①</span><div><b>当前页大图</b><p class="dim">占视图一半以上,保证你能扫一眼就知道观众现在看到什么。</p></div></div>
|
||||
<div class="feature-row"><span class="num green">②</span><div><b>下一页预览</b><p class="dim">帮你提前准备过渡句,避免"下一页我忘了讲什么了"。</p></div></div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="feature-row"><span class="num orange">③</span><div><b>逐字稿区域</b><p class="dim">大字号、高对比度、支持滚动,这才是演讲者真正在看的东西。</p></div></div>
|
||||
<div class="feature-row"><span class="num purple">④</span><div><b>计时器 + 页码</b><p class="dim">知道自己讲了多久、还剩几页,节奏全凭这个。</p></div></div>
|
||||
</div>
|
||||
</div>
|
||||
<aside class="notes">
|
||||
<p>演讲者模式应该给你四块信息。我按重要性排序。</p>
|
||||
<p>第一块,<strong>当前页大图</strong>。这个必须占据视图一半以上空间,因为它是你跟观众的"同步锚"——观众看到什么,你脑子里也得是什么。</p>
|
||||
<p>第二块,<strong>下一页预览</strong>。这个很多人不理解为什么要放,我解释一下:演讲最卡的瞬间不是讲某一页,而是<em>翻到下一页的那 2 秒</em>。如果你提前看到下一页长什么样,过渡句自然就有了。</p>
|
||||
<p>第三块,<strong>逐字稿区域</strong>——这是今天的重点,下一部分我会专门讲。这里先说一个硬性要求:字号必须大、对比度必须高、必须能滚动。因为你讲的时候<em>只有余光瞄一下</em>,字小了根本来不及读。</p>
|
||||
<p>第四块,<strong>计时器和页码</strong>。知道自己讲了多久、还剩几页——节奏感全靠它。Keynote 做得最好,reveal.js 默认不够清楚。</p>
|
||||
<p>这四块缺一不可。今天这个 deck 我把这四块都做出来了,按 S 大家可以试试。</p>
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
<!-- ============ 5. SCRIPT ============ -->
|
||||
<section class="slide" data-title="Script">
|
||||
<p class="kicker">// part 03 · script</p>
|
||||
<h2 class="h2">逐字稿的<span class="accent">3 条铁律</span></h2>
|
||||
<div class="stack mt-l">
|
||||
<div class="rule-row">
|
||||
<span class="num red">01</span>
|
||||
<div>
|
||||
<b>不是一字不差的讲稿,是<span class="accent">"提示信号"</span></b>
|
||||
<p class="dim">把要讲的核心点加粗,把过渡句单独成段,把数据和名字列清楚——<em>让你看一眼就接得上</em>。</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rule-row">
|
||||
<span class="num red">02</span>
|
||||
<div>
|
||||
<b>每页 <span class="accent">150–300 字</span>,不多不少</b>
|
||||
<p class="dim">少于 150 字提示不够,多于 300 字你没时间读。按 2–3 分钟/页的节奏控制。</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rule-row">
|
||||
<span class="num red">03</span>
|
||||
<div>
|
||||
<b>用<span class="accent">口语</span>写,不用书面语</b>
|
||||
<p class="dim">"因此" → "所以";"该方案" → "这个方案"。写的时候读一遍,听起来像说话才对。</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<aside class="notes">
|
||||
<p>进入最核心的一部分——逐字稿怎么写。我总结了 3 条铁律。</p>
|
||||
<p><strong>第一条,逐字稿不是讲稿</strong>。很多人一听"逐字稿"就以为要把每句话一字不差写下来。错。如果你照着稿念,观众会立刻看出来,信任感瞬间崩塌。</p>
|
||||
<p>逐字稿的真实作用是<em>"提示信号"</em>——把核心要点加粗,把过渡句单独成段,把数据和专有名词列清楚。这样你讲的时候<strong>瞄一眼就能接得上</strong>,但说出来的还是你自己的话。</p>
|
||||
<p><strong>第二条,每页控制在 150 到 300 字</strong>。这个是我做了十几场分享摸出来的经验值。少于 150 字提示不够,讲到一半卡住;多于 300 字你根本来不及扫完。按一页讲 2 到 3 分钟算,这个字数刚好。</p>
|
||||
<p><strong>第三条,用口语写</strong>。这条最多人栽跟头。你写"因此",讲出来会变成"所以";你写"该方案",讲出来会变成"这个方案"。<em>写的时候读一遍</em>,不拗口才对。</p>
|
||||
<p>这三条配合起来,你会发现讲 PPT 突然变成了一件很舒服的事。</p>
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
<!-- ============ 6. DEMO + CLOSING ============ -->
|
||||
<section class="slide" data-title="Demo & Close">
|
||||
<p class="kicker">// part 04-05 · demo + close</p>
|
||||
<h2 class="h2">现在<span class="accent">你也能做到</span></h2>
|
||||
<div class="code-block mt-m">
|
||||
<span class="comment"># 安装 html-ppt skill</span>
|
||||
<span class="cmd">npx</span> skills add <span class="flag">https://github.com/lewislulu/html-ppt-skill</span>
|
||||
|
||||
<span class="comment"># 复制演讲者模式模板</span>
|
||||
<span class="cmd">cp -r</span> templates/full-decks/presenter-mode-reveal examples/my-talk
|
||||
<span class="cmd">open</span> examples/my-talk/index.html
|
||||
|
||||
<span class="comment"># 键盘操作</span>
|
||||
<span class="flag">S</span> <span class="comment">→ 进入演讲者视图</span>
|
||||
<span class="flag">T</span> <span class="comment">→ 切换主题(5 种预设)</span>
|
||||
<span class="flag">← →</span> <span class="comment">→ 翻页</span>
|
||||
<span class="flag">R</span> <span class="comment">→ 重置计时器</span>
|
||||
</div>
|
||||
<p class="lede mt-m tc">关键是:<strong>每一页 <aside class="notes"> 里写 150–300 字逐字稿</strong>。</p>
|
||||
<div class="deck-footer">
|
||||
<span class="mono">#thanks · Q&A</span>
|
||||
<span class="slide-number" data-current="6" data-total="6"></span>
|
||||
</div>
|
||||
<aside class="notes">
|
||||
<p>最后我演示一下这个 skill 怎么用,给大家省点时间自己摸索。</p>
|
||||
<p>第一步,装 html-ppt skill,一行命令。第二步,把我这个 <code>presenter-mode-reveal</code> 模板复制到你自己的 examples 目录。第三步,打开 html,按 S。</p>
|
||||
<p>键盘操作我列在这里了。<strong>S 进入演讲者视图、T 切换主题、左右键翻页、R 重置计时器</strong>。主题默认带 5 个——tokyo-night、dracula、catppuccin-mocha、nord、corporate-clean——基本覆盖了深色技术分享、浅色商务汇报两种常见场景。</p>
|
||||
<p>最关键的一步——<em>每一页底部的 <code><aside class="notes"></code> 里,老老实实写 150 到 300 字的逐字稿</em>。这是整个方法论的交付物。AI 可以帮你写初稿,但你一定要自己过一遍,读出来听听是不是你会说的话。</p>
|
||||
<p>好,我今天就讲到这里。如果你做下一场分享的时候想起了这个"演讲者视图 + 逐字稿"的组合,并且觉得讲得比以前顺——那就是我最大的收获。谢谢大家,有问题现在开始。</p>
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
<div style="position:fixed;bottom:12px;left:12px;font-size:11px;color:#484f5866;z-index:100;pointer-events:none">
|
||||
S 演讲者视图 · T 切换主题 · ← → 翻页 · F 全屏 · O 总览 · R 重置计时
|
||||
</div>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
78
skills/html-ppt-product-launch/SKILL.md
Normal file
78
skills/html-ppt-product-launch/SKILL.md
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
---
|
||||
name: html-ppt-product-launch
|
||||
description: Launch keynote deck — dark hero + light content, warm orange→peach accent, feature cards, pricing tiers, CTA. Use when announcing a product, launching a feature, or doing a keynote-style reveal.
|
||||
triggers:
|
||||
- "product launch"
|
||||
- "keynote"
|
||||
- "launch deck"
|
||||
- "feature reveal"
|
||||
- "launch slides"
|
||||
- "发布会"
|
||||
od:
|
||||
mode: deck
|
||||
scenario: marketing
|
||||
featured: 21
|
||||
upstream: "https://github.com/lewislulu/html-ppt-skill"
|
||||
preview:
|
||||
type: html
|
||||
entry: index.html
|
||||
design_system:
|
||||
requires: false
|
||||
speaker_notes: true
|
||||
animations: true
|
||||
example_prompt: "Make a product-launch keynote deck in HTML using the html-ppt-product-launch full-deck template (dark hero, warm orange accent, feature cards, pricing tiers). Confirm: product name + tagline, the 3 key features, and pricing tiers — then write the deck."
|
||||
---
|
||||
# HTML PPT · Product Launch
|
||||
|
||||
A focused entry point into the [`html-ppt`](../html-ppt/SKILL.md) master skill that lands the user directly on the **`product-launch`** full-deck template.
|
||||
|
||||
## When this card is picked
|
||||
|
||||
The Examples gallery wires "Use this prompt" to the example_prompt above. When you accept that prompt, this card is the right pick if the user wants exactly the visual identity of `product-launch` (see the upstream [full-decks catalog](../html-ppt/references/full-decks.md) for screenshots and rationale).
|
||||
|
||||
## How to author the deck
|
||||
|
||||
1. **Read the master skill first.** All authoring rules live in
|
||||
[`skills/html-ppt/SKILL.md`](../html-ppt/SKILL.md) — content/audience checklist,
|
||||
token rules, layout reuse, presenter mode, the keyboard runtime, and the
|
||||
"never put presenter-only text on the slide" rule.
|
||||
2. **Start from the matching template folder:**
|
||||
`skills/html-ppt/templates/full-decks/product-launch/` — copy `index.html` and
|
||||
`style.css` into the project, keep the `.tpl-product-launch` body class.
|
||||
3. **Bring the shared runtime with the template.** The upstream
|
||||
`index.html` links the shared CSS/JS via `../../../assets/...` because it
|
||||
sits three folders deep inside `skills/html-ppt/templates/full-decks/`.
|
||||
Once you copy `index.html` into the project, those parent-relative URLs
|
||||
no longer resolve and `base.css`, `animations.css`, and `runtime.js`
|
||||
will 404 — meaning the deck never activates and slide navigation is
|
||||
dead. Pick one of these two recipes per project:
|
||||
- **Recipe A — copy + rewrite (preferred):** copy
|
||||
`skills/html-ppt/assets/fonts.css`, `skills/html-ppt/assets/base.css`,
|
||||
`skills/html-ppt/assets/animations/animations.css`, and
|
||||
`skills/html-ppt/assets/runtime.js` into a project-local
|
||||
`assets/` (with `assets/animations/animations.css`), then rewrite the
|
||||
four `<link>`/`<script>` tags in `index.html` from
|
||||
`../../../assets/...` to the matching project-local paths
|
||||
(`assets/fonts.css`, `assets/base.css`,
|
||||
`assets/animations/animations.css`, `assets/runtime.js`).
|
||||
- **Recipe B — inline:** read the same four files and replace each
|
||||
`<link rel="stylesheet" href="../../../assets/...">` with a
|
||||
`<style>...</style>` containing the file's contents, and the
|
||||
`<script src="../../../assets/runtime.js">` with a
|
||||
`<script>...</script>` containing `runtime.js`. Yields a single
|
||||
self-contained `index.html`.
|
||||
Either way, do not ship the upstream `../../../assets/...` URLs
|
||||
verbatim into a project artifact — they only work in-tree.
|
||||
4. **Pick a theme.** Default tokens look fine; if the user wants a different
|
||||
feel, swap in any of the 36 themes from `skills/html-ppt/assets/themes/*.css`
|
||||
via `<link id="theme-link">` and let `T` cycle.
|
||||
5. **Replace demo content, not classes.** The `.tpl-product-launch` scoped CSS only
|
||||
recognises the structural classes shipped in the template — keep them.
|
||||
6. **Speaker notes go inside `<aside class="notes">` or `<div class="notes">`** — never as visible text on the slide.
|
||||
|
||||
## Attribution
|
||||
|
||||
Visual system, layouts, themes and the runtime keyboard model come from
|
||||
the upstream MIT-licensed [`lewislulu/html-ppt-skill`](https://github.com/lewislulu/html-ppt-skill). The
|
||||
LICENSE file ships at `skills/html-ppt/LICENSE`; please keep it in place when
|
||||
redistributing.
|
||||
467
skills/html-ppt-product-launch/example.html
Normal file
467
skills/html-ppt-product-launch/example.html
Normal file
|
|
@ -0,0 +1,467 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Halo v2 · Launch</title>
|
||||
<style>/* html-ppt :: shared webfonts */
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@200;300;400;500;600;700;800;900&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@200;300;400;500;600;700;900&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@300;400;600;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,600;0,800;1,400&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@300;400;500;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Archivo+Black&display=swap');
|
||||
|
||||
</style>
|
||||
<style>/* html-ppt :: base.css — reset + shared tokens + layout primitives */
|
||||
/* Default tokens. Themes in assets/themes/*.css override the :root block. */
|
||||
:root {
|
||||
--bg: #ffffff;
|
||||
--bg-soft: #f7f7f8;
|
||||
--surface: #ffffff;
|
||||
--surface-2: #f2f2f4;
|
||||
--border: rgba(0,0,0,.08);
|
||||
--border-strong: rgba(0,0,0,.16);
|
||||
--text-1: #111216;
|
||||
--text-2: #55596a;
|
||||
--text-3: #8a8f9e;
|
||||
--accent: #3b6cff;
|
||||
--accent-2: #7a5cff;
|
||||
--accent-3: #ff5c8a;
|
||||
--good: #1aaf6c;
|
||||
--warn: #f5a524;
|
||||
--bad: #e0445a;
|
||||
--grad: linear-gradient(135deg,#3b6cff,#7a5cff 55%,#ff5c8a);
|
||||
--grad-soft: linear-gradient(135deg,#eef2ff,#f5ecff 55%,#ffeef5);
|
||||
--radius: 18px;
|
||||
--radius-sm: 12px;
|
||||
--radius-lg: 26px;
|
||||
--shadow: 0 10px 30px rgba(18,24,40,.08), 0 2px 6px rgba(18,24,40,.04);
|
||||
--shadow-lg: 0 24px 60px rgba(18,24,40,.14), 0 6px 16px rgba(18,24,40,.06);
|
||||
--font-sans: 'Inter','Noto Sans SC',-apple-system,BlinkMacSystemFont,Helvetica,Arial,sans-serif;
|
||||
--font-serif: 'Playfair Display','Noto Serif SC',Georgia,serif;
|
||||
--font-mono: 'JetBrains Mono','IBM Plex Mono',SFMono-Regular,Menlo,monospace;
|
||||
--font-display: var(--font-sans);
|
||||
--letter-tight: -.03em;
|
||||
--letter-normal: -.01em;
|
||||
--ease: cubic-bezier(.4,0,.2,1);
|
||||
}
|
||||
|
||||
*,*::before,*::after{box-sizing:border-box}
|
||||
html,body{margin:0;padding:0;background:var(--bg);color:var(--text-1);
|
||||
font-family:var(--font-sans);font-weight:400;line-height:1.6;
|
||||
-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;
|
||||
letter-spacing:var(--letter-normal)}
|
||||
img,svg,video{max-width:100%;display:block}
|
||||
a{color:var(--accent);text-decoration:none}
|
||||
a:hover{text-decoration:underline}
|
||||
code,kbd,pre,samp{font-family:var(--font-mono)}
|
||||
|
||||
/* ================= SLIDE SYSTEM ================= */
|
||||
.deck{position:relative;width:100vw;height:100vh;overflow:hidden;background:var(--bg)}
|
||||
.slide{
|
||||
position:absolute;inset:0;
|
||||
display:flex;flex-direction:column;justify-content:center;
|
||||
padding:72px 96px;
|
||||
box-sizing:border-box;
|
||||
opacity:0;pointer-events:none;
|
||||
transition:opacity .5s var(--ease), transform .5s var(--ease);
|
||||
transform:translateX(30px);
|
||||
overflow:hidden;
|
||||
}
|
||||
.slide.is-active{opacity:1;pointer-events:auto;transform:translateX(0);z-index:2}
|
||||
.slide.is-prev{transform:translateX(-30px)}
|
||||
|
||||
/* single-page standalone (used when a layout file is opened directly) */
|
||||
body.single .slide{position:relative;width:100vw;height:100vh;opacity:1;transform:none;pointer-events:auto}
|
||||
|
||||
/* ================= TYPOGRAPHY ================= */
|
||||
.eyebrow{font-size:13px;font-weight:500;letter-spacing:.16em;text-transform:uppercase;color:var(--text-3)}
|
||||
.kicker{font-size:14px;font-weight:600;color:var(--accent);letter-spacing:.08em;text-transform:uppercase}
|
||||
h1.title,.h1{font-family:var(--font-display);font-size:72px;line-height:1.05;font-weight:800;letter-spacing:var(--letter-tight);margin:0 0 18px;color:var(--text-1)}
|
||||
h2.title,.h2{font-family:var(--font-display);font-size:54px;line-height:1.1;font-weight:700;letter-spacing:var(--letter-tight);margin:0 0 14px}
|
||||
h3,.h3{font-size:32px;line-height:1.2;font-weight:600;letter-spacing:var(--letter-normal);margin:0 0 10px}
|
||||
h4,.h4{font-size:22px;line-height:1.3;font-weight:600;margin:0 0 8px}
|
||||
.lede{font-size:22px;line-height:1.55;color:var(--text-2);font-weight:300;max-width:62ch}
|
||||
.dim{color:var(--text-2)}
|
||||
.dim2{color:var(--text-3)}
|
||||
.mono{font-family:var(--font-mono)}
|
||||
.serif{font-family:var(--font-serif)}
|
||||
.gradient-text{background:var(--grad);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;color:transparent}
|
||||
|
||||
/* ================= LAYOUT PRIMITIVES ================= */
|
||||
.stack>*+*{margin-top:14px}
|
||||
.row{display:flex;gap:24px;align-items:center}
|
||||
.row.wrap{flex-wrap:wrap}
|
||||
.grid{display:grid;gap:24px}
|
||||
.g2{grid-template-columns:repeat(2,1fr)}
|
||||
.g3{grid-template-columns:repeat(3,1fr)}
|
||||
.g4{grid-template-columns:repeat(4,1fr)}
|
||||
.center{display:flex;align-items:center;justify-content:center;text-align:center}
|
||||
.fill{flex:1}
|
||||
.sp-t{padding-top:24px}.sp-b{padding-bottom:24px}
|
||||
.mt-s{margin-top:8px}.mt-m{margin-top:18px}.mt-l{margin-top:32px}
|
||||
.mb-s{margin-bottom:8px}.mb-m{margin-bottom:18px}.mb-l{margin-bottom:32px}
|
||||
|
||||
/* ================= CARDS ================= */
|
||||
.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);
|
||||
padding:26px 28px;box-shadow:var(--shadow);position:relative;overflow:hidden}
|
||||
.card-soft{background:var(--surface-2);border:1px solid var(--border)}
|
||||
.card-outline{background:transparent;border:1.5px solid var(--border-strong);box-shadow:none}
|
||||
.card-accent{background:var(--surface);border-top:3px solid var(--accent)}
|
||||
.card-hover{transition:transform .3s var(--ease),box-shadow .3s var(--ease)}
|
||||
.card-hover:hover{transform:translateY(-4px);box-shadow:var(--shadow-lg)}
|
||||
|
||||
.pill{display:inline-block;padding:4px 12px;border-radius:999px;font-size:12px;font-weight:500;
|
||||
background:var(--surface-2);color:var(--text-2);border:1px solid var(--border)}
|
||||
.pill-accent{background:color-mix(in srgb,var(--accent) 12%,transparent);color:var(--accent);border-color:color-mix(in srgb,var(--accent) 28%,transparent)}
|
||||
|
||||
/* ================= BARS / DIVIDERS ================= */
|
||||
.divider{height:1px;background:var(--border);width:100%}
|
||||
.divider-accent{height:3px;width:72px;background:var(--accent);border-radius:2px}
|
||||
|
||||
/* ================= CHROME (header/footer/progress) ================= */
|
||||
.deck-header{position:absolute;top:24px;left:40px;right:40px;display:flex;align-items:center;justify-content:space-between;
|
||||
font-size:12px;color:var(--text-3);letter-spacing:.12em;text-transform:uppercase;z-index:10;pointer-events:none}
|
||||
.deck-footer{position:absolute;bottom:24px;left:40px;right:40px;display:flex;align-items:center;justify-content:space-between;
|
||||
font-size:12px;color:var(--text-3);z-index:10;pointer-events:none}
|
||||
.slide-number::before{content:attr(data-current)}
|
||||
.slide-number::after{content:" / " attr(data-total)}
|
||||
.progress-bar{position:fixed;left:0;right:0;bottom:0;height:3px;background:transparent;z-index:20}
|
||||
.progress-bar > span{display:block;height:100%;width:0;background:var(--accent);transition:width .3s var(--ease)}
|
||||
|
||||
/* ================= PRESENTER / OVERVIEW ================= */
|
||||
.notes{display:none!important}
|
||||
.notes-overlay{position:fixed;inset:auto 0 0 0;max-height:42vh;background:rgba(20,22,30,.95);color:#e8ebf4;
|
||||
padding:20px 32px;font-size:16px;line-height:1.6;border-top:1px solid rgba(255,255,255,.1);transform:translateY(100%);
|
||||
transition:transform .3s var(--ease);z-index:40;overflow:auto;font-family:var(--font-sans)}
|
||||
.notes-overlay.open{transform:translateY(0)}
|
||||
.overview{position:fixed;inset:0;background:rgba(10,12,18,.92);backdrop-filter:blur(12px);z-index:50;
|
||||
display:none;padding:40px;overflow:auto}
|
||||
.overview.open{display:grid;grid-template-columns:repeat(4,1fr);gap:20px;align-content:start}
|
||||
.overview .thumb{background:var(--surface);border:1px solid var(--border);border-radius:12px;
|
||||
aspect-ratio:16/9;overflow:hidden;cursor:pointer;position:relative;color:var(--text-1);padding:16px;
|
||||
font-size:11px;transition:transform .2s var(--ease)}
|
||||
.overview .thumb:hover{transform:scale(1.04)}
|
||||
.overview .thumb .n{position:absolute;top:8px;left:10px;font-weight:700;font-size:14px;color:var(--text-3)}
|
||||
.overview .thumb .t{position:absolute;bottom:10px;left:14px;right:14px;font-weight:600;color:var(--text-1)}
|
||||
|
||||
/* ================= PRESENTER VIEW ================= */
|
||||
/* Presenter view opens in a separate popup window (S key).
|
||||
* All presenter styles are self-contained in the popup HTML generated by runtime.js.
|
||||
* The audience window (this file) is NOT affected — it stays as normal deck view.
|
||||
* Only the .notes class below is needed to hide speaker notes from audience. */
|
||||
|
||||
/* ================= UTILITY ================= */
|
||||
.hidden{display:none!important}
|
||||
.nowrap{white-space:nowrap}
|
||||
.tr{text-align:right}.tc{text-align:center}.tl{text-align:left}
|
||||
.uppercase{text-transform:uppercase;letter-spacing:.12em}
|
||||
|
||||
/* ================= PRINT ================= */
|
||||
@media print{
|
||||
.slide{position:relative;opacity:1!important;transform:none!important;page-break-after:always;height:100vh}
|
||||
.deck-header,.deck-footer,.progress-bar,.notes-overlay,.overview{display:none!important}
|
||||
}
|
||||
|
||||
</style>
|
||||
<style>/* html-ppt :: animations.css
|
||||
* Apply by adding class="anim-<name>" or data-anim="<name>".
|
||||
* Durations are deliberately snappy; tweak --anim-dur per element.
|
||||
*/
|
||||
:root{--anim-dur:.7s;--anim-ease:cubic-bezier(.4,0,.2,1)}
|
||||
|
||||
/* ---------- FADE DIRECTIONALS ---------- */
|
||||
@keyframes kf-fade-up{from{opacity:0;transform:translateY(32px)}to{opacity:1;transform:none}}
|
||||
@keyframes kf-fade-down{from{opacity:0;transform:translateY(-32px)}to{opacity:1;transform:none}}
|
||||
@keyframes kf-fade-left{from{opacity:0;transform:translateX(-40px)}to{opacity:1;transform:none}}
|
||||
@keyframes kf-fade-right{from{opacity:0;transform:translateX(40px)}to{opacity:1;transform:none}}
|
||||
.anim-fade-up{animation:kf-fade-up var(--anim-dur) var(--anim-ease) both}
|
||||
.anim-fade-down{animation:kf-fade-down var(--anim-dur) var(--anim-ease) both}
|
||||
.anim-fade-left{animation:kf-fade-left var(--anim-dur) var(--anim-ease) both}
|
||||
.anim-fade-right{animation:kf-fade-right var(--anim-dur) var(--anim-ease) both}
|
||||
|
||||
/* ---------- RISE / DROP / ZOOM / BLUR / GLITCH ---------- */
|
||||
@keyframes kf-rise{from{opacity:0;transform:translateY(60px) scale(.97);filter:blur(6px)}to{opacity:1;transform:none;filter:none}}
|
||||
@keyframes kf-drop{from{opacity:0;transform:translateY(-60px) scale(.97)}to{opacity:1;transform:none}}
|
||||
@keyframes kf-zoom{0%{opacity:0;transform:scale(.6)}60%{transform:scale(1.04)}100%{opacity:1;transform:scale(1)}}
|
||||
@keyframes kf-blur{from{opacity:0;filter:blur(18px)}to{opacity:1;filter:none}}
|
||||
@keyframes kf-glitch{0%{opacity:0;transform:translateX(0);clip-path:inset(0 0 0 0)}
|
||||
20%{opacity:1;transform:translateX(-6px);clip-path:inset(20% 0 30% 0)}
|
||||
40%{transform:translateX(4px);clip-path:inset(50% 0 10% 0)}
|
||||
60%{transform:translateX(-3px);clip-path:inset(10% 0 60% 0)}
|
||||
80%{transform:translateX(2px);clip-path:inset(0 0 0 0)}
|
||||
100%{opacity:1;transform:none}}
|
||||
.anim-rise-in{animation:kf-rise .9s var(--anim-ease) both}
|
||||
.anim-drop-in{animation:kf-drop .8s var(--anim-ease) both}
|
||||
.anim-zoom-pop{animation:kf-zoom .7s cubic-bezier(.22,1.3,.36,1) both}
|
||||
.anim-blur-in{animation:kf-blur .8s var(--anim-ease) both}
|
||||
.anim-glitch-in{animation:kf-glitch .8s steps(5,end) both}
|
||||
|
||||
/* ---------- TYPEWRITER ---------- */
|
||||
.anim-typewriter{display:inline-block;overflow:hidden;white-space:nowrap;border-right:2px solid currentColor;
|
||||
width:0;animation:kf-type 2.4s steps(40,end) forwards, kf-caret 1s step-end infinite}
|
||||
@keyframes kf-type{to{width:100%}}
|
||||
@keyframes kf-caret{50%{border-color:transparent}}
|
||||
|
||||
/* ---------- GLOW / SHIMMER / GRADIENT-FLOW ---------- */
|
||||
@keyframes kf-neon{0%,100%{text-shadow:0 0 8px var(--accent),0 0 20px var(--accent)}
|
||||
50%{text-shadow:0 0 16px var(--accent),0 0 40px var(--accent),0 0 80px var(--accent)}}
|
||||
.anim-neon-glow{animation:kf-neon 2s ease-in-out infinite}
|
||||
|
||||
.anim-shimmer-sweep{position:relative;overflow:hidden}
|
||||
.anim-shimmer-sweep::after{content:"";position:absolute;inset:0;
|
||||
background:linear-gradient(110deg,transparent 40%,rgba(255,255,255,.55) 50%,transparent 60%);
|
||||
transform:translateX(-100%);animation:kf-shimmer 2.4s var(--anim-ease) infinite}
|
||||
@keyframes kf-shimmer{to{transform:translateX(100%)}}
|
||||
|
||||
.anim-gradient-flow{background:linear-gradient(90deg,var(--accent),var(--accent-2,var(--accent)),var(--accent-3,var(--accent)),var(--accent));
|
||||
background-size:300% 100%;-webkit-background-clip:text;background-clip:text;color:transparent;-webkit-text-fill-color:transparent;
|
||||
animation:kf-gradflow 4s linear infinite}
|
||||
@keyframes kf-gradflow{to{background-position:300% 0}}
|
||||
|
||||
/* ---------- STAGGER LIST ---------- */
|
||||
.anim-stagger-list > *{opacity:0;animation:kf-rise .65s var(--anim-ease) both}
|
||||
.anim-stagger-list > *:nth-child(1){animation-delay:.05s}
|
||||
.anim-stagger-list > *:nth-child(2){animation-delay:.15s}
|
||||
.anim-stagger-list > *:nth-child(3){animation-delay:.25s}
|
||||
.anim-stagger-list > *:nth-child(4){animation-delay:.35s}
|
||||
.anim-stagger-list > *:nth-child(5){animation-delay:.45s}
|
||||
.anim-stagger-list > *:nth-child(6){animation-delay:.55s}
|
||||
.anim-stagger-list > *:nth-child(7){animation-delay:.65s}
|
||||
.anim-stagger-list > *:nth-child(8){animation-delay:.75s}
|
||||
.anim-stagger-list > *:nth-child(n+9){animation-delay:.85s}
|
||||
|
||||
/* ---------- COUNTER-UP (JS-driven, marker class only) ---------- */
|
||||
.counter{font-variant-numeric:tabular-nums}
|
||||
|
||||
/* ---------- SVG PATH DRAW ---------- */
|
||||
.anim-path-draw path,.anim-path-draw line,.anim-path-draw polyline,.anim-path-draw circle,.anim-path-draw rect{
|
||||
stroke-dasharray:1000;stroke-dashoffset:1000;animation:kf-draw 2s var(--anim-ease) forwards}
|
||||
@keyframes kf-draw{to{stroke-dashoffset:0}}
|
||||
|
||||
/* ---------- PARALLAX TILT (hover) ---------- */
|
||||
.anim-parallax-tilt{transform-style:preserve-3d;transition:transform .4s var(--anim-ease)}
|
||||
.anim-parallax-tilt:hover{transform:perspective(900px) rotateX(6deg) rotateY(-8deg) translateZ(10px)}
|
||||
|
||||
/* ---------- CARD FLIP 3D ---------- */
|
||||
@keyframes kf-flip{from{transform:perspective(1200px) rotateY(-90deg);opacity:0}
|
||||
to{transform:perspective(1200px) rotateY(0);opacity:1}}
|
||||
.anim-card-flip-3d{animation:kf-flip .9s var(--anim-ease) both;transform-style:preserve-3d;backface-visibility:hidden}
|
||||
|
||||
/* ---------- CUBE ROTATE 3D ---------- */
|
||||
@keyframes kf-cube{from{transform:perspective(1200px) rotateX(20deg) rotateY(-90deg) translateZ(-200px);opacity:0}
|
||||
to{transform:perspective(1200px) rotateX(0) rotateY(0) translateZ(0);opacity:1}}
|
||||
.anim-cube-rotate-3d{animation:kf-cube 1s var(--anim-ease) both}
|
||||
|
||||
/* ---------- PAGE TURN 3D ---------- */
|
||||
@keyframes kf-pageturn{from{transform:perspective(1600px) rotateY(-85deg);transform-origin:left center;opacity:0}
|
||||
to{transform:perspective(1600px) rotateY(0);opacity:1}}
|
||||
.anim-page-turn-3d{animation:kf-pageturn 1s var(--anim-ease) both;transform-origin:left center}
|
||||
|
||||
/* ---------- PERSPECTIVE ZOOM ---------- */
|
||||
@keyframes kf-pzoom{from{opacity:0;transform:perspective(1400px) translateZ(-400px) rotateX(12deg)}
|
||||
to{opacity:1;transform:none}}
|
||||
.anim-perspective-zoom{animation:kf-pzoom 1s var(--anim-ease) both}
|
||||
|
||||
/* ---------- MARQUEE SCROLL ---------- */
|
||||
.anim-marquee-scroll{display:flex;gap:48px;white-space:nowrap;animation:kf-marquee 20s linear infinite}
|
||||
@keyframes kf-marquee{from{transform:translateX(0)}to{transform:translateX(-50%)}}
|
||||
|
||||
/* ---------- KEN BURNS ---------- */
|
||||
@keyframes kf-kenburns{0%{transform:scale(1) translate(0,0)}100%{transform:scale(1.15) translate(-2%,-1%)}}
|
||||
.anim-kenburns{animation:kf-kenburns 14s ease-in-out infinite alternate}
|
||||
|
||||
/* ---------- CONFETTI BURST (pseudo — pure CSS sparkles) ---------- */
|
||||
.anim-confetti-burst{position:relative}
|
||||
.anim-confetti-burst::before,.anim-confetti-burst::after{
|
||||
content:"";position:absolute;top:50%;left:50%;width:8px;height:8px;border-radius:50%;
|
||||
background:var(--accent);box-shadow:
|
||||
20px -30px 0 var(--accent-2,var(--accent)),-25px -20px 0 var(--accent-3,var(--accent)),
|
||||
30px 20px 0 var(--good,#1aaf6c),-30px 25px 0 var(--warn,#f5a524),
|
||||
40px -10px 0 var(--bad,#e0445a),-45px 0 0 var(--accent),
|
||||
10px 40px 0 var(--accent-2,var(--accent)),-15px -40px 0 var(--accent-3,var(--accent));
|
||||
opacity:0;animation:kf-confetti 1.2s var(--anim-ease) forwards}
|
||||
.anim-confetti-burst::after{animation-delay:.15s;transform:rotate(45deg)}
|
||||
@keyframes kf-confetti{0%{opacity:0;transform:scale(.2)}30%{opacity:1}100%{opacity:0;transform:scale(2.2)}}
|
||||
|
||||
/* ---------- SPOTLIGHT ---------- */
|
||||
@keyframes kf-spot{0%{clip-path:circle(0% at 50% 50%)}100%{clip-path:circle(140% at 50% 50%)}}
|
||||
.anim-spotlight{animation:kf-spot 1.1s var(--anim-ease) both}
|
||||
|
||||
/* ---------- MORPH SHAPE (SVG) ---------- */
|
||||
.anim-morph-shape path{animation:kf-morph 6s ease-in-out infinite alternate}
|
||||
@keyframes kf-morph{0%{d:path("M60,120 Q120,20 180,120 T300,120")}
|
||||
100%{d:path("M60,120 Q120,220 180,120 T300,120")}}
|
||||
|
||||
/* ---------- RIPPLE REVEAL ---------- */
|
||||
@keyframes kf-ripple{0%{clip-path:circle(0% at 20% 80%);opacity:.4}
|
||||
100%{clip-path:circle(160% at 20% 80%);opacity:1}}
|
||||
.anim-ripple-reveal{animation:kf-ripple 1.2s var(--anim-ease) both}
|
||||
|
||||
/* reduced motion */
|
||||
@media (prefers-reduced-motion: reduce){
|
||||
[class*="anim-"]{animation:none!important;transition:none!important}
|
||||
}
|
||||
|
||||
</style>
|
||||
<style>/* product-launch — modern announcement deck */
|
||||
.tpl-product-launch{
|
||||
--bg:#ffffff;--bg-soft:#f5f5f7;--surface:#ffffff;--surface-2:#f2f2f6;
|
||||
--ink:#0a0a12;--ink-2:#3a3a44;
|
||||
--border:rgba(10,10,18,.08);--border-strong:rgba(10,10,18,.18);
|
||||
--text-1:#0a0a12;--text-2:#4a4a58;--text-3:#8a8a96;
|
||||
--accent:#ff5a36;--accent-2:#ff8c5a;--accent-3:#ffb36b;
|
||||
--grad:linear-gradient(120deg,#ff5a36 0%,#ff8c5a 60%,#ffb36b 100%);
|
||||
--radius:22px;--radius-lg:32px;
|
||||
--shadow:0 20px 60px rgba(10,10,18,.1);
|
||||
font-family:'Inter','Noto Sans SC',sans-serif;
|
||||
}
|
||||
.tpl-product-launch .slide{padding:80px 112px}
|
||||
.tpl-product-launch .slide.dark{background:#0a0a12;color:#f5f5f7}
|
||||
.tpl-product-launch .slide.dark .h1,.tpl-product-launch .slide.dark .h2,.tpl-product-launch .slide.dark h3,.tpl-product-launch .slide.dark h4{color:#fff}
|
||||
.tpl-product-launch .slide.dark .lede,.tpl-product-launch .slide.dark .dim{color:rgba(245,245,247,.72)}
|
||||
.tpl-product-launch .slide.dark .card{background:rgba(255,255,255,.06);border-color:rgba(255,255,255,.12);box-shadow:none;backdrop-filter:blur(20px)}
|
||||
.tpl-product-launch .slide.dark .kicker{color:var(--accent-2)}
|
||||
.tpl-product-launch .h1{font-size:96px;line-height:.98;font-weight:900;letter-spacing:-.045em}
|
||||
.tpl-product-launch .h2{font-size:64px;font-weight:800;letter-spacing:-.035em}
|
||||
.tpl-product-launch .hero-shot{position:absolute;right:-60px;top:50%;transform:translateY(-50%);width:640px;height:640px;border-radius:50%;background:var(--grad);filter:blur(2px);opacity:.85}
|
||||
.tpl-product-launch .hero-shot::after{content:"";position:absolute;inset:80px;border-radius:40px;background:linear-gradient(160deg,rgba(255,255,255,.3),transparent 60%),#1a1a28;box-shadow:inset 0 2px 0 rgba(255,255,255,.2)}
|
||||
.tpl-product-launch .hero-shot::before{content:"Halo v2";position:absolute;inset:80px;display:flex;align-items:center;justify-content:center;color:#fff;font-size:44px;font-weight:900;letter-spacing:-.02em;z-index:2;border-radius:40px}
|
||||
.tpl-product-launch .brand{font-size:18px;font-weight:800;letter-spacing:-.02em}
|
||||
.tpl-product-launch .feature-card{padding:40px 36px;border-radius:var(--radius-lg);background:var(--surface);border:1px solid var(--border);position:relative;overflow:hidden}
|
||||
.tpl-product-launch .feature-card .icon{width:60px;height:60px;border-radius:18px;background:var(--grad);display:flex;align-items:center;justify-content:center;color:#fff;font-size:28px;font-weight:900;margin-bottom:20px}
|
||||
.tpl-product-launch .step{display:flex;gap:24px;align-items:flex-start}
|
||||
.tpl-product-launch .step .n{flex:none;width:56px;height:56px;border-radius:50%;background:var(--grad);color:#fff;display:flex;align-items:center;justify-content:center;font-weight:900;font-size:22px}
|
||||
.tpl-product-launch .price-card{padding:40px 32px;border-radius:var(--radius-lg);border:1.5px solid var(--border);background:var(--surface);text-align:left}
|
||||
.tpl-product-launch .price-card.pro{background:#0a0a12;color:#fff;border-color:#0a0a12;transform:scale(1.04);box-shadow:0 30px 80px rgba(255,90,54,.25)}
|
||||
.tpl-product-launch .price-card.pro .dim{color:rgba(255,255,255,.7)}
|
||||
.tpl-product-launch .price-card h4{font-size:16px;text-transform:uppercase;letter-spacing:.1em;color:var(--accent)}
|
||||
.tpl-product-launch .price-card.pro h4{color:var(--accent-2)}
|
||||
.tpl-product-launch .price-card .amount{font-size:64px;font-weight:900;letter-spacing:-.035em;margin:14px 0}
|
||||
.tpl-product-launch .price-card ul{list-style:none;padding:0;margin:20px 0 0}
|
||||
.tpl-product-launch .price-card li{padding:8px 0;font-size:15px;color:var(--text-2);border-top:1px solid var(--border)}
|
||||
.tpl-product-launch .price-card.pro li{color:rgba(255,255,255,.8);border-color:rgba(255,255,255,.12)}
|
||||
.tpl-product-launch .cta-btn{display:inline-block;padding:20px 40px;border-radius:999px;background:var(--grad);color:#fff;font-weight:700;font-size:20px;box-shadow:0 20px 50px rgba(255,90,54,.4)}
|
||||
.tpl-product-launch .testimonial{max-width:44ch;font-family:'Playfair Display',serif;font-size:44px;line-height:1.25;font-weight:500;letter-spacing:-.01em}
|
||||
|
||||
</style>
|
||||
<style>
|
||||
/* Static-preview fallback (runtime.js is absent — keep every slide visible) */
|
||||
.deck{height:auto;min-height:100vh;overflow:visible}
|
||||
.slide{position:relative;inset:auto;opacity:1;pointer-events:auto;transform:none;height:100vh;page-break-after:always}
|
||||
.deck-header,.deck-footer,.slide-number,.progress-bar,.notes-overlay,.overview{pointer-events:none}
|
||||
.notes{display:none!important}
|
||||
</style></head>
|
||||
<body class="tpl-product-launch">
|
||||
<div class="deck">
|
||||
|
||||
<!-- 1. Cover / hero -->
|
||||
<section class="slide dark" data-title="Cover">
|
||||
<div class="hero-shot"></div>
|
||||
<div style="position:absolute;top:56px;left:112px" class="brand">◎ Halo</div>
|
||||
<p class="kicker">Launch · April 2026</p>
|
||||
<h1 class="h1 anim-fade-up" data-anim="fade-up">Meet Halo v2.<br>Your ears,<br><span style="background:var(--grad);-webkit-background-clip:text;background-clip:text;color:transparent">rewritten.</span></h1>
|
||||
<p class="lede mt-m" style="max-width:42ch">Studio-grade spatial audio in the lightest open-ear earbuds ever made.</p>
|
||||
<div class="deck-footer"><span>halo.audio</span><span class="slide-number" data-current="1" data-total="8"></span></div>
|
||||
</section>
|
||||
|
||||
<!-- 2. Introducing -->
|
||||
<section class="slide center tc" data-title="Introducing">
|
||||
<div>
|
||||
<p class="kicker">Introducing</p>
|
||||
<h1 class="h1" style="font-size:140px">Halo v2</h1>
|
||||
<p class="lede" style="margin:24px auto;max-width:56ch">Four years of research. Three generations of silicon. One product you'll forget you're wearing.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 3. Feature 1 -->
|
||||
<section class="slide" data-title="Sound">
|
||||
<p class="kicker">01 · The sound</p>
|
||||
<h2 class="h2">Hear the room<br>around the music.</h2>
|
||||
<div class="grid g3 mt-l">
|
||||
<div class="feature-card"><div class="icon">♪</div><h4>Open-ear spatial</h4><p class="dim">16mm titanium drivers angled into the ear canal. You hear the song and the world at once.</p></div>
|
||||
<div class="feature-card"><div class="icon">◈</div><h4>Lossless 24-bit</h4><p class="dim">aptX Lossless and Hi-Res LDAC over Bluetooth 5.4. No dongles, no compromises.</p></div>
|
||||
<div class="feature-card"><div class="icon">◐</div><h4>Adaptive EQ</h4><p class="dim">Tunes itself to the shape of your ear every 120 seconds.</p></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 4. Feature 2 -->
|
||||
<section class="slide dark" data-title="Fit">
|
||||
<p class="kicker">02 · The fit</p>
|
||||
<h2 class="h2">4.9 grams.<br>All-day forgettable.</h2>
|
||||
<div class="grid g3 mt-l">
|
||||
<div class="card"><h4>Liquid-silicone hook</h4><p>Wraps behind the ear like a glasses arm. Never falls out on a run.</p></div>
|
||||
<div class="card"><h4>IP57 sweat + rain</h4><p>Take them in the ocean. Rinse them under the tap. We dare you.</p></div>
|
||||
<div class="card"><h4>14h + 42h case</h4><p>A full workweek of commutes on one charge of the case.</p></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 5. Feature 3 -->
|
||||
<section class="slide" data-title="Intelligence">
|
||||
<p class="kicker">03 · The intelligence</p>
|
||||
<h2 class="h2">An AI that listens<br>so you don't have to.</h2>
|
||||
<div class="grid g2 mt-l">
|
||||
<div class="feature-card"><div class="icon">✦</div><h4>Live translate</h4><p class="dim">Real-time translation in 41 languages. Whispered directly into your ear, with a 380ms lag.</p></div>
|
||||
<div class="feature-card"><div class="icon">✧</div><h4>Meeting recap</h4><p class="dim">Double-tap to record. Walk away with a summary, action items, and a searchable transcript.</p></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 6. How it works -->
|
||||
<section class="slide" data-title="How it works">
|
||||
<p class="kicker">How it works</p>
|
||||
<h2 class="h2">Three taps. You're in.</h2>
|
||||
<div class="stack mt-l" style="max-width:900px">
|
||||
<div class="step"><div class="n">1</div><div><h4>Open the case near your phone</h4><p class="dim">iOS and Android pair automatically over Bluetooth LE. No app downloads required.</p></div></div>
|
||||
<div class="step"><div class="n">2</div><div><h4>Pick your profile</h4><p class="dim">Commute, Focus, Workout, Cinema. Each is a complete audio + transparency recipe.</p></div></div>
|
||||
<div class="step"><div class="n">3</div><div><h4>Just listen</h4><p class="dim">Halo adapts to your ear shape, your environment, and your hearing profile — continuously.</p></div></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 7. Pricing -->
|
||||
<section class="slide" data-title="Pricing">
|
||||
<p class="kicker">Pricing</p>
|
||||
<h2 class="h2">Pick your Halo.</h2>
|
||||
<div class="grid g3 mt-l" style="align-items:start">
|
||||
<div class="price-card">
|
||||
<h4>Halo Lite</h4>
|
||||
<div class="amount">$179</div>
|
||||
<p class="dim">Open-ear audio, IP57, 12h battery.</p>
|
||||
<ul><li>AAC + SBC</li><li>Single-tap controls</li><li>USB-C charging</li></ul>
|
||||
</div>
|
||||
<div class="price-card pro">
|
||||
<h4>Halo v2 · Pro</h4>
|
||||
<div class="amount">$279</div>
|
||||
<p class="dim">Everything, in its best form.</p>
|
||||
<ul><li>Hi-Res Lossless</li><li>Live translate · 41 lang</li><li>Wireless + MagSafe charging</li><li>Adaptive EQ</li></ul>
|
||||
</div>
|
||||
<div class="price-card">
|
||||
<h4>Halo Studio</h4>
|
||||
<div class="amount">$399</div>
|
||||
<p class="dim">For creators and field recorders.</p>
|
||||
<ul><li>32-bit binaural capture</li><li>XLR dongle included</li><li>Lifetime firmware</li></ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 8. Testimonial + CTA combined? Task says 8 slides w/ testimonial + CTA as separate. Keep 8: testimonial on 7, but we've used 7 already. Re-plan: cover(1) intro(2) f1(3) f2(4) f3(5) how(6) pricing(7) testimonial+CTA(8) -->
|
||||
<section class="slide dark" data-title="Ship">
|
||||
<p class="kicker">One more thing</p>
|
||||
<div class="row" style="gap:80px;align-items:center">
|
||||
<div style="flex:1">
|
||||
<p class="testimonial">"I forgot I was wearing them. Then I remembered, and I didn't want to take them off."</p>
|
||||
<p class="dim mt-m">— Marques Lin, The Verge · early review</p>
|
||||
</div>
|
||||
<div style="flex:0 0 auto;text-align:center">
|
||||
<p class="dim mb-m">Ships May 14 · from</p>
|
||||
<div style="font-size:96px;font-weight:900;letter-spacing:-.04em">$279</div>
|
||||
<a class="cta-btn mt-l" href="#">Pre-order Halo v2 →</a>
|
||||
<p class="dim mt-m" style="font-size:13px">Free shipping · 45-day return · 2-year warranty</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
</body></html>
|
||||
78
skills/html-ppt-tech-sharing/SKILL.md
Normal file
78
skills/html-ppt-tech-sharing/SKILL.md
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
---
|
||||
name: html-ppt-tech-sharing
|
||||
description: Conference / internal tech-talk deck — GitHub-dark, JetBrains Mono, terminal code blocks, agenda + Q&A pages. Use for engineering presentations, internal sharing sessions, conference talks, and code-heavy walkthroughs.
|
||||
triggers:
|
||||
- "tech sharing"
|
||||
- "tech talk"
|
||||
- "技术分享"
|
||||
- "engineering talk"
|
||||
- "conference talk"
|
||||
- "dev talk"
|
||||
od:
|
||||
mode: deck
|
||||
scenario: engineering
|
||||
featured: 22
|
||||
upstream: "https://github.com/lewislulu/html-ppt-skill"
|
||||
preview:
|
||||
type: html
|
||||
entry: index.html
|
||||
design_system:
|
||||
requires: false
|
||||
speaker_notes: true
|
||||
animations: true
|
||||
example_prompt: "帮我用 html-ppt-tech-sharing 模板做一份 8 页的技术分享 PPT。先确认:分享主题、目标听众(同事 / 社区 / 客户)、要不要包含代码片段和 benchmark。GitHub 暗色主题 + JetBrains Mono,agenda + Q&A 页备好。"
|
||||
---
|
||||
# HTML PPT · Tech Sharing
|
||||
|
||||
A focused entry point into the [`html-ppt`](../html-ppt/SKILL.md) master skill that lands the user directly on the **`tech-sharing`** full-deck template.
|
||||
|
||||
## When this card is picked
|
||||
|
||||
The Examples gallery wires "Use this prompt" to the example_prompt above. When you accept that prompt, this card is the right pick if the user wants exactly the visual identity of `tech-sharing` (see the upstream [full-decks catalog](../html-ppt/references/full-decks.md) for screenshots and rationale).
|
||||
|
||||
## How to author the deck
|
||||
|
||||
1. **Read the master skill first.** All authoring rules live in
|
||||
[`skills/html-ppt/SKILL.md`](../html-ppt/SKILL.md) — content/audience checklist,
|
||||
token rules, layout reuse, presenter mode, the keyboard runtime, and the
|
||||
"never put presenter-only text on the slide" rule.
|
||||
2. **Start from the matching template folder:**
|
||||
`skills/html-ppt/templates/full-decks/tech-sharing/` — copy `index.html` and
|
||||
`style.css` into the project, keep the `.tpl-tech-sharing` body class.
|
||||
3. **Bring the shared runtime with the template.** The upstream
|
||||
`index.html` links the shared CSS/JS via `../../../assets/...` because it
|
||||
sits three folders deep inside `skills/html-ppt/templates/full-decks/`.
|
||||
Once you copy `index.html` into the project, those parent-relative URLs
|
||||
no longer resolve and `base.css`, `animations.css`, and `runtime.js`
|
||||
will 404 — meaning the deck never activates and slide navigation is
|
||||
dead. Pick one of these two recipes per project:
|
||||
- **Recipe A — copy + rewrite (preferred):** copy
|
||||
`skills/html-ppt/assets/fonts.css`, `skills/html-ppt/assets/base.css`,
|
||||
`skills/html-ppt/assets/animations/animations.css`, and
|
||||
`skills/html-ppt/assets/runtime.js` into a project-local
|
||||
`assets/` (with `assets/animations/animations.css`), then rewrite the
|
||||
four `<link>`/`<script>` tags in `index.html` from
|
||||
`../../../assets/...` to the matching project-local paths
|
||||
(`assets/fonts.css`, `assets/base.css`,
|
||||
`assets/animations/animations.css`, `assets/runtime.js`).
|
||||
- **Recipe B — inline:** read the same four files and replace each
|
||||
`<link rel="stylesheet" href="../../../assets/...">` with a
|
||||
`<style>...</style>` containing the file's contents, and the
|
||||
`<script src="../../../assets/runtime.js">` with a
|
||||
`<script>...</script>` containing `runtime.js`. Yields a single
|
||||
self-contained `index.html`.
|
||||
Either way, do not ship the upstream `../../../assets/...` URLs
|
||||
verbatim into a project artifact — they only work in-tree.
|
||||
4. **Pick a theme.** Default tokens look fine; if the user wants a different
|
||||
feel, swap in any of the 36 themes from `skills/html-ppt/assets/themes/*.css`
|
||||
via `<link id="theme-link">` and let `T` cycle.
|
||||
5. **Replace demo content, not classes.** The `.tpl-tech-sharing` scoped CSS only
|
||||
recognises the structural classes shipped in the template — keep them.
|
||||
6. **Speaker notes go inside `<aside class="notes">` or `<div class="notes">`** — never as visible text on the slide.
|
||||
|
||||
## Attribution
|
||||
|
||||
Visual system, layouts, themes and the runtime keyboard model come from
|
||||
the upstream MIT-licensed [`lewislulu/html-ppt-skill`](https://github.com/lewislulu/html-ppt-skill). The
|
||||
LICENSE file ships at `skills/html-ppt/LICENSE`; please keep it in place when
|
||||
redistributing.
|
||||
512
skills/html-ppt-tech-sharing/example.html
Normal file
512
skills/html-ppt-tech-sharing/example.html
Normal file
|
|
@ -0,0 +1,512 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Rust 异步运行时内部机制 · Tech Sharing</title>
|
||||
<style>/* html-ppt :: shared webfonts */
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@200;300;400;500;600;700;800;900&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@200;300;400;500;600;700;900&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@300;400;600;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,600;0,800;1,400&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@300;400;500;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Archivo+Black&display=swap');
|
||||
|
||||
</style>
|
||||
<style>/* html-ppt :: base.css — reset + shared tokens + layout primitives */
|
||||
/* Default tokens. Themes in assets/themes/*.css override the :root block. */
|
||||
:root {
|
||||
--bg: #ffffff;
|
||||
--bg-soft: #f7f7f8;
|
||||
--surface: #ffffff;
|
||||
--surface-2: #f2f2f4;
|
||||
--border: rgba(0,0,0,.08);
|
||||
--border-strong: rgba(0,0,0,.16);
|
||||
--text-1: #111216;
|
||||
--text-2: #55596a;
|
||||
--text-3: #8a8f9e;
|
||||
--accent: #3b6cff;
|
||||
--accent-2: #7a5cff;
|
||||
--accent-3: #ff5c8a;
|
||||
--good: #1aaf6c;
|
||||
--warn: #f5a524;
|
||||
--bad: #e0445a;
|
||||
--grad: linear-gradient(135deg,#3b6cff,#7a5cff 55%,#ff5c8a);
|
||||
--grad-soft: linear-gradient(135deg,#eef2ff,#f5ecff 55%,#ffeef5);
|
||||
--radius: 18px;
|
||||
--radius-sm: 12px;
|
||||
--radius-lg: 26px;
|
||||
--shadow: 0 10px 30px rgba(18,24,40,.08), 0 2px 6px rgba(18,24,40,.04);
|
||||
--shadow-lg: 0 24px 60px rgba(18,24,40,.14), 0 6px 16px rgba(18,24,40,.06);
|
||||
--font-sans: 'Inter','Noto Sans SC',-apple-system,BlinkMacSystemFont,Helvetica,Arial,sans-serif;
|
||||
--font-serif: 'Playfair Display','Noto Serif SC',Georgia,serif;
|
||||
--font-mono: 'JetBrains Mono','IBM Plex Mono',SFMono-Regular,Menlo,monospace;
|
||||
--font-display: var(--font-sans);
|
||||
--letter-tight: -.03em;
|
||||
--letter-normal: -.01em;
|
||||
--ease: cubic-bezier(.4,0,.2,1);
|
||||
}
|
||||
|
||||
*,*::before,*::after{box-sizing:border-box}
|
||||
html,body{margin:0;padding:0;background:var(--bg);color:var(--text-1);
|
||||
font-family:var(--font-sans);font-weight:400;line-height:1.6;
|
||||
-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;
|
||||
letter-spacing:var(--letter-normal)}
|
||||
img,svg,video{max-width:100%;display:block}
|
||||
a{color:var(--accent);text-decoration:none}
|
||||
a:hover{text-decoration:underline}
|
||||
code,kbd,pre,samp{font-family:var(--font-mono)}
|
||||
|
||||
/* ================= SLIDE SYSTEM ================= */
|
||||
.deck{position:relative;width:100vw;height:100vh;overflow:hidden;background:var(--bg)}
|
||||
.slide{
|
||||
position:absolute;inset:0;
|
||||
display:flex;flex-direction:column;justify-content:center;
|
||||
padding:72px 96px;
|
||||
box-sizing:border-box;
|
||||
opacity:0;pointer-events:none;
|
||||
transition:opacity .5s var(--ease), transform .5s var(--ease);
|
||||
transform:translateX(30px);
|
||||
overflow:hidden;
|
||||
}
|
||||
.slide.is-active{opacity:1;pointer-events:auto;transform:translateX(0);z-index:2}
|
||||
.slide.is-prev{transform:translateX(-30px)}
|
||||
|
||||
/* single-page standalone (used when a layout file is opened directly) */
|
||||
body.single .slide{position:relative;width:100vw;height:100vh;opacity:1;transform:none;pointer-events:auto}
|
||||
|
||||
/* ================= TYPOGRAPHY ================= */
|
||||
.eyebrow{font-size:13px;font-weight:500;letter-spacing:.16em;text-transform:uppercase;color:var(--text-3)}
|
||||
.kicker{font-size:14px;font-weight:600;color:var(--accent);letter-spacing:.08em;text-transform:uppercase}
|
||||
h1.title,.h1{font-family:var(--font-display);font-size:72px;line-height:1.05;font-weight:800;letter-spacing:var(--letter-tight);margin:0 0 18px;color:var(--text-1)}
|
||||
h2.title,.h2{font-family:var(--font-display);font-size:54px;line-height:1.1;font-weight:700;letter-spacing:var(--letter-tight);margin:0 0 14px}
|
||||
h3,.h3{font-size:32px;line-height:1.2;font-weight:600;letter-spacing:var(--letter-normal);margin:0 0 10px}
|
||||
h4,.h4{font-size:22px;line-height:1.3;font-weight:600;margin:0 0 8px}
|
||||
.lede{font-size:22px;line-height:1.55;color:var(--text-2);font-weight:300;max-width:62ch}
|
||||
.dim{color:var(--text-2)}
|
||||
.dim2{color:var(--text-3)}
|
||||
.mono{font-family:var(--font-mono)}
|
||||
.serif{font-family:var(--font-serif)}
|
||||
.gradient-text{background:var(--grad);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;color:transparent}
|
||||
|
||||
/* ================= LAYOUT PRIMITIVES ================= */
|
||||
.stack>*+*{margin-top:14px}
|
||||
.row{display:flex;gap:24px;align-items:center}
|
||||
.row.wrap{flex-wrap:wrap}
|
||||
.grid{display:grid;gap:24px}
|
||||
.g2{grid-template-columns:repeat(2,1fr)}
|
||||
.g3{grid-template-columns:repeat(3,1fr)}
|
||||
.g4{grid-template-columns:repeat(4,1fr)}
|
||||
.center{display:flex;align-items:center;justify-content:center;text-align:center}
|
||||
.fill{flex:1}
|
||||
.sp-t{padding-top:24px}.sp-b{padding-bottom:24px}
|
||||
.mt-s{margin-top:8px}.mt-m{margin-top:18px}.mt-l{margin-top:32px}
|
||||
.mb-s{margin-bottom:8px}.mb-m{margin-bottom:18px}.mb-l{margin-bottom:32px}
|
||||
|
||||
/* ================= CARDS ================= */
|
||||
.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);
|
||||
padding:26px 28px;box-shadow:var(--shadow);position:relative;overflow:hidden}
|
||||
.card-soft{background:var(--surface-2);border:1px solid var(--border)}
|
||||
.card-outline{background:transparent;border:1.5px solid var(--border-strong);box-shadow:none}
|
||||
.card-accent{background:var(--surface);border-top:3px solid var(--accent)}
|
||||
.card-hover{transition:transform .3s var(--ease),box-shadow .3s var(--ease)}
|
||||
.card-hover:hover{transform:translateY(-4px);box-shadow:var(--shadow-lg)}
|
||||
|
||||
.pill{display:inline-block;padding:4px 12px;border-radius:999px;font-size:12px;font-weight:500;
|
||||
background:var(--surface-2);color:var(--text-2);border:1px solid var(--border)}
|
||||
.pill-accent{background:color-mix(in srgb,var(--accent) 12%,transparent);color:var(--accent);border-color:color-mix(in srgb,var(--accent) 28%,transparent)}
|
||||
|
||||
/* ================= BARS / DIVIDERS ================= */
|
||||
.divider{height:1px;background:var(--border);width:100%}
|
||||
.divider-accent{height:3px;width:72px;background:var(--accent);border-radius:2px}
|
||||
|
||||
/* ================= CHROME (header/footer/progress) ================= */
|
||||
.deck-header{position:absolute;top:24px;left:40px;right:40px;display:flex;align-items:center;justify-content:space-between;
|
||||
font-size:12px;color:var(--text-3);letter-spacing:.12em;text-transform:uppercase;z-index:10;pointer-events:none}
|
||||
.deck-footer{position:absolute;bottom:24px;left:40px;right:40px;display:flex;align-items:center;justify-content:space-between;
|
||||
font-size:12px;color:var(--text-3);z-index:10;pointer-events:none}
|
||||
.slide-number::before{content:attr(data-current)}
|
||||
.slide-number::after{content:" / " attr(data-total)}
|
||||
.progress-bar{position:fixed;left:0;right:0;bottom:0;height:3px;background:transparent;z-index:20}
|
||||
.progress-bar > span{display:block;height:100%;width:0;background:var(--accent);transition:width .3s var(--ease)}
|
||||
|
||||
/* ================= PRESENTER / OVERVIEW ================= */
|
||||
.notes{display:none!important}
|
||||
.notes-overlay{position:fixed;inset:auto 0 0 0;max-height:42vh;background:rgba(20,22,30,.95);color:#e8ebf4;
|
||||
padding:20px 32px;font-size:16px;line-height:1.6;border-top:1px solid rgba(255,255,255,.1);transform:translateY(100%);
|
||||
transition:transform .3s var(--ease);z-index:40;overflow:auto;font-family:var(--font-sans)}
|
||||
.notes-overlay.open{transform:translateY(0)}
|
||||
.overview{position:fixed;inset:0;background:rgba(10,12,18,.92);backdrop-filter:blur(12px);z-index:50;
|
||||
display:none;padding:40px;overflow:auto}
|
||||
.overview.open{display:grid;grid-template-columns:repeat(4,1fr);gap:20px;align-content:start}
|
||||
.overview .thumb{background:var(--surface);border:1px solid var(--border);border-radius:12px;
|
||||
aspect-ratio:16/9;overflow:hidden;cursor:pointer;position:relative;color:var(--text-1);padding:16px;
|
||||
font-size:11px;transition:transform .2s var(--ease)}
|
||||
.overview .thumb:hover{transform:scale(1.04)}
|
||||
.overview .thumb .n{position:absolute;top:8px;left:10px;font-weight:700;font-size:14px;color:var(--text-3)}
|
||||
.overview .thumb .t{position:absolute;bottom:10px;left:14px;right:14px;font-weight:600;color:var(--text-1)}
|
||||
|
||||
/* ================= PRESENTER VIEW ================= */
|
||||
/* Presenter view opens in a separate popup window (S key).
|
||||
* All presenter styles are self-contained in the popup HTML generated by runtime.js.
|
||||
* The audience window (this file) is NOT affected — it stays as normal deck view.
|
||||
* Only the .notes class below is needed to hide speaker notes from audience. */
|
||||
|
||||
/* ================= UTILITY ================= */
|
||||
.hidden{display:none!important}
|
||||
.nowrap{white-space:nowrap}
|
||||
.tr{text-align:right}.tc{text-align:center}.tl{text-align:left}
|
||||
.uppercase{text-transform:uppercase;letter-spacing:.12em}
|
||||
|
||||
/* ================= PRINT ================= */
|
||||
@media print{
|
||||
.slide{position:relative;opacity:1!important;transform:none!important;page-break-after:always;height:100vh}
|
||||
.deck-header,.deck-footer,.progress-bar,.notes-overlay,.overview{display:none!important}
|
||||
}
|
||||
|
||||
</style>
|
||||
<style>/* html-ppt :: animations.css
|
||||
* Apply by adding class="anim-<name>" or data-anim="<name>".
|
||||
* Durations are deliberately snappy; tweak --anim-dur per element.
|
||||
*/
|
||||
:root{--anim-dur:.7s;--anim-ease:cubic-bezier(.4,0,.2,1)}
|
||||
|
||||
/* ---------- FADE DIRECTIONALS ---------- */
|
||||
@keyframes kf-fade-up{from{opacity:0;transform:translateY(32px)}to{opacity:1;transform:none}}
|
||||
@keyframes kf-fade-down{from{opacity:0;transform:translateY(-32px)}to{opacity:1;transform:none}}
|
||||
@keyframes kf-fade-left{from{opacity:0;transform:translateX(-40px)}to{opacity:1;transform:none}}
|
||||
@keyframes kf-fade-right{from{opacity:0;transform:translateX(40px)}to{opacity:1;transform:none}}
|
||||
.anim-fade-up{animation:kf-fade-up var(--anim-dur) var(--anim-ease) both}
|
||||
.anim-fade-down{animation:kf-fade-down var(--anim-dur) var(--anim-ease) both}
|
||||
.anim-fade-left{animation:kf-fade-left var(--anim-dur) var(--anim-ease) both}
|
||||
.anim-fade-right{animation:kf-fade-right var(--anim-dur) var(--anim-ease) both}
|
||||
|
||||
/* ---------- RISE / DROP / ZOOM / BLUR / GLITCH ---------- */
|
||||
@keyframes kf-rise{from{opacity:0;transform:translateY(60px) scale(.97);filter:blur(6px)}to{opacity:1;transform:none;filter:none}}
|
||||
@keyframes kf-drop{from{opacity:0;transform:translateY(-60px) scale(.97)}to{opacity:1;transform:none}}
|
||||
@keyframes kf-zoom{0%{opacity:0;transform:scale(.6)}60%{transform:scale(1.04)}100%{opacity:1;transform:scale(1)}}
|
||||
@keyframes kf-blur{from{opacity:0;filter:blur(18px)}to{opacity:1;filter:none}}
|
||||
@keyframes kf-glitch{0%{opacity:0;transform:translateX(0);clip-path:inset(0 0 0 0)}
|
||||
20%{opacity:1;transform:translateX(-6px);clip-path:inset(20% 0 30% 0)}
|
||||
40%{transform:translateX(4px);clip-path:inset(50% 0 10% 0)}
|
||||
60%{transform:translateX(-3px);clip-path:inset(10% 0 60% 0)}
|
||||
80%{transform:translateX(2px);clip-path:inset(0 0 0 0)}
|
||||
100%{opacity:1;transform:none}}
|
||||
.anim-rise-in{animation:kf-rise .9s var(--anim-ease) both}
|
||||
.anim-drop-in{animation:kf-drop .8s var(--anim-ease) both}
|
||||
.anim-zoom-pop{animation:kf-zoom .7s cubic-bezier(.22,1.3,.36,1) both}
|
||||
.anim-blur-in{animation:kf-blur .8s var(--anim-ease) both}
|
||||
.anim-glitch-in{animation:kf-glitch .8s steps(5,end) both}
|
||||
|
||||
/* ---------- TYPEWRITER ---------- */
|
||||
.anim-typewriter{display:inline-block;overflow:hidden;white-space:nowrap;border-right:2px solid currentColor;
|
||||
width:0;animation:kf-type 2.4s steps(40,end) forwards, kf-caret 1s step-end infinite}
|
||||
@keyframes kf-type{to{width:100%}}
|
||||
@keyframes kf-caret{50%{border-color:transparent}}
|
||||
|
||||
/* ---------- GLOW / SHIMMER / GRADIENT-FLOW ---------- */
|
||||
@keyframes kf-neon{0%,100%{text-shadow:0 0 8px var(--accent),0 0 20px var(--accent)}
|
||||
50%{text-shadow:0 0 16px var(--accent),0 0 40px var(--accent),0 0 80px var(--accent)}}
|
||||
.anim-neon-glow{animation:kf-neon 2s ease-in-out infinite}
|
||||
|
||||
.anim-shimmer-sweep{position:relative;overflow:hidden}
|
||||
.anim-shimmer-sweep::after{content:"";position:absolute;inset:0;
|
||||
background:linear-gradient(110deg,transparent 40%,rgba(255,255,255,.55) 50%,transparent 60%);
|
||||
transform:translateX(-100%);animation:kf-shimmer 2.4s var(--anim-ease) infinite}
|
||||
@keyframes kf-shimmer{to{transform:translateX(100%)}}
|
||||
|
||||
.anim-gradient-flow{background:linear-gradient(90deg,var(--accent),var(--accent-2,var(--accent)),var(--accent-3,var(--accent)),var(--accent));
|
||||
background-size:300% 100%;-webkit-background-clip:text;background-clip:text;color:transparent;-webkit-text-fill-color:transparent;
|
||||
animation:kf-gradflow 4s linear infinite}
|
||||
@keyframes kf-gradflow{to{background-position:300% 0}}
|
||||
|
||||
/* ---------- STAGGER LIST ---------- */
|
||||
.anim-stagger-list > *{opacity:0;animation:kf-rise .65s var(--anim-ease) both}
|
||||
.anim-stagger-list > *:nth-child(1){animation-delay:.05s}
|
||||
.anim-stagger-list > *:nth-child(2){animation-delay:.15s}
|
||||
.anim-stagger-list > *:nth-child(3){animation-delay:.25s}
|
||||
.anim-stagger-list > *:nth-child(4){animation-delay:.35s}
|
||||
.anim-stagger-list > *:nth-child(5){animation-delay:.45s}
|
||||
.anim-stagger-list > *:nth-child(6){animation-delay:.55s}
|
||||
.anim-stagger-list > *:nth-child(7){animation-delay:.65s}
|
||||
.anim-stagger-list > *:nth-child(8){animation-delay:.75s}
|
||||
.anim-stagger-list > *:nth-child(n+9){animation-delay:.85s}
|
||||
|
||||
/* ---------- COUNTER-UP (JS-driven, marker class only) ---------- */
|
||||
.counter{font-variant-numeric:tabular-nums}
|
||||
|
||||
/* ---------- SVG PATH DRAW ---------- */
|
||||
.anim-path-draw path,.anim-path-draw line,.anim-path-draw polyline,.anim-path-draw circle,.anim-path-draw rect{
|
||||
stroke-dasharray:1000;stroke-dashoffset:1000;animation:kf-draw 2s var(--anim-ease) forwards}
|
||||
@keyframes kf-draw{to{stroke-dashoffset:0}}
|
||||
|
||||
/* ---------- PARALLAX TILT (hover) ---------- */
|
||||
.anim-parallax-tilt{transform-style:preserve-3d;transition:transform .4s var(--anim-ease)}
|
||||
.anim-parallax-tilt:hover{transform:perspective(900px) rotateX(6deg) rotateY(-8deg) translateZ(10px)}
|
||||
|
||||
/* ---------- CARD FLIP 3D ---------- */
|
||||
@keyframes kf-flip{from{transform:perspective(1200px) rotateY(-90deg);opacity:0}
|
||||
to{transform:perspective(1200px) rotateY(0);opacity:1}}
|
||||
.anim-card-flip-3d{animation:kf-flip .9s var(--anim-ease) both;transform-style:preserve-3d;backface-visibility:hidden}
|
||||
|
||||
/* ---------- CUBE ROTATE 3D ---------- */
|
||||
@keyframes kf-cube{from{transform:perspective(1200px) rotateX(20deg) rotateY(-90deg) translateZ(-200px);opacity:0}
|
||||
to{transform:perspective(1200px) rotateX(0) rotateY(0) translateZ(0);opacity:1}}
|
||||
.anim-cube-rotate-3d{animation:kf-cube 1s var(--anim-ease) both}
|
||||
|
||||
/* ---------- PAGE TURN 3D ---------- */
|
||||
@keyframes kf-pageturn{from{transform:perspective(1600px) rotateY(-85deg);transform-origin:left center;opacity:0}
|
||||
to{transform:perspective(1600px) rotateY(0);opacity:1}}
|
||||
.anim-page-turn-3d{animation:kf-pageturn 1s var(--anim-ease) both;transform-origin:left center}
|
||||
|
||||
/* ---------- PERSPECTIVE ZOOM ---------- */
|
||||
@keyframes kf-pzoom{from{opacity:0;transform:perspective(1400px) translateZ(-400px) rotateX(12deg)}
|
||||
to{opacity:1;transform:none}}
|
||||
.anim-perspective-zoom{animation:kf-pzoom 1s var(--anim-ease) both}
|
||||
|
||||
/* ---------- MARQUEE SCROLL ---------- */
|
||||
.anim-marquee-scroll{display:flex;gap:48px;white-space:nowrap;animation:kf-marquee 20s linear infinite}
|
||||
@keyframes kf-marquee{from{transform:translateX(0)}to{transform:translateX(-50%)}}
|
||||
|
||||
/* ---------- KEN BURNS ---------- */
|
||||
@keyframes kf-kenburns{0%{transform:scale(1) translate(0,0)}100%{transform:scale(1.15) translate(-2%,-1%)}}
|
||||
.anim-kenburns{animation:kf-kenburns 14s ease-in-out infinite alternate}
|
||||
|
||||
/* ---------- CONFETTI BURST (pseudo — pure CSS sparkles) ---------- */
|
||||
.anim-confetti-burst{position:relative}
|
||||
.anim-confetti-burst::before,.anim-confetti-burst::after{
|
||||
content:"";position:absolute;top:50%;left:50%;width:8px;height:8px;border-radius:50%;
|
||||
background:var(--accent);box-shadow:
|
||||
20px -30px 0 var(--accent-2,var(--accent)),-25px -20px 0 var(--accent-3,var(--accent)),
|
||||
30px 20px 0 var(--good,#1aaf6c),-30px 25px 0 var(--warn,#f5a524),
|
||||
40px -10px 0 var(--bad,#e0445a),-45px 0 0 var(--accent),
|
||||
10px 40px 0 var(--accent-2,var(--accent)),-15px -40px 0 var(--accent-3,var(--accent));
|
||||
opacity:0;animation:kf-confetti 1.2s var(--anim-ease) forwards}
|
||||
.anim-confetti-burst::after{animation-delay:.15s;transform:rotate(45deg)}
|
||||
@keyframes kf-confetti{0%{opacity:0;transform:scale(.2)}30%{opacity:1}100%{opacity:0;transform:scale(2.2)}}
|
||||
|
||||
/* ---------- SPOTLIGHT ---------- */
|
||||
@keyframes kf-spot{0%{clip-path:circle(0% at 50% 50%)}100%{clip-path:circle(140% at 50% 50%)}}
|
||||
.anim-spotlight{animation:kf-spot 1.1s var(--anim-ease) both}
|
||||
|
||||
/* ---------- MORPH SHAPE (SVG) ---------- */
|
||||
.anim-morph-shape path{animation:kf-morph 6s ease-in-out infinite alternate}
|
||||
@keyframes kf-morph{0%{d:path("M60,120 Q120,20 180,120 T300,120")}
|
||||
100%{d:path("M60,120 Q120,220 180,120 T300,120")}}
|
||||
|
||||
/* ---------- RIPPLE REVEAL ---------- */
|
||||
@keyframes kf-ripple{0%{clip-path:circle(0% at 20% 80%);opacity:.4}
|
||||
100%{clip-path:circle(160% at 20% 80%);opacity:1}}
|
||||
.anim-ripple-reveal{animation:kf-ripple 1.2s var(--anim-ease) both}
|
||||
|
||||
/* reduced motion */
|
||||
@media (prefers-reduced-motion: reduce){
|
||||
[class*="anim-"]{animation:none!important;transition:none!important}
|
||||
}
|
||||
|
||||
</style>
|
||||
<style>/* tech-sharing — 技术分享 dark, code-forward */
|
||||
.tpl-tech-sharing{
|
||||
--bg:#0d1117;--bg-soft:#161b22;--surface:#161b22;--surface-2:#1c2230;
|
||||
--border:rgba(139,148,158,.22);--border-strong:rgba(139,148,158,.4);
|
||||
--text-1:#e6edf3;--text-2:#8b949e;--text-3:#6e7681;
|
||||
--accent:#7ee787;--accent-2:#79c0ff;--accent-3:#ff7b72;
|
||||
--grad:linear-gradient(120deg,#7ee787 0%,#79c0ff 60%,#d2a8ff 100%);
|
||||
--radius:14px;--radius-lg:20px;
|
||||
--shadow:0 20px 60px rgba(0,0,0,.5);
|
||||
font-family:'Inter','Noto Sans SC',sans-serif;
|
||||
}
|
||||
.tpl-tech-sharing{background:#0d1117;color:var(--text-1)}
|
||||
.tpl-tech-sharing .slide{padding:72px 96px;background:#0d1117;color:var(--text-1)}
|
||||
.tpl-tech-sharing .slide::before{content:"";position:absolute;inset:0;background:
|
||||
radial-gradient(60% 50% at 90% 10%,rgba(121,192,255,.12),transparent 60%),
|
||||
radial-gradient(50% 50% at 10% 90%,rgba(126,231,135,.08),transparent 60%);
|
||||
pointer-events:none;z-index:0}
|
||||
.tpl-tech-sharing .slide>*{position:relative;z-index:1}
|
||||
.tpl-tech-sharing .h1{font-size:78px;line-height:1.03;font-weight:800;letter-spacing:-.03em;color:#fff}
|
||||
.tpl-tech-sharing .h2{font-size:54px;font-weight:700;letter-spacing:-.025em;color:#fff}
|
||||
.tpl-tech-sharing h3,.tpl-tech-sharing h4{color:#fff}
|
||||
.tpl-tech-sharing .kicker{color:var(--accent);font-family:'JetBrains Mono',monospace;font-size:13px;font-weight:600;text-transform:none;letter-spacing:.02em}
|
||||
.tpl-tech-sharing .kicker::before{content:"> "}
|
||||
.tpl-tech-sharing .mono{font-family:'JetBrains Mono','IBM Plex Mono',monospace}
|
||||
.tpl-tech-sharing .terminal{background:#010409;border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;box-shadow:0 30px 80px rgba(0,0,0,.6);font-family:'JetBrains Mono',monospace;font-size:15px;line-height:1.65}
|
||||
.tpl-tech-sharing .terminal .bar{display:flex;align-items:center;gap:8px;padding:12px 16px;background:#161b22;border-bottom:1px solid var(--border);font-size:12px;color:var(--text-3)}
|
||||
.tpl-tech-sharing .terminal .dot{width:12px;height:12px;border-radius:50%;background:#ff5f56}
|
||||
.tpl-tech-sharing .terminal .dot:nth-child(2){background:#ffbd2e}
|
||||
.tpl-tech-sharing .terminal .dot:nth-child(3){background:#27c93f}
|
||||
.tpl-tech-sharing .terminal pre{margin:0;padding:24px 28px;color:#e6edf3;overflow:auto;max-height:440px}
|
||||
.tpl-tech-sharing .kw{color:#ff7b72}
|
||||
.tpl-tech-sharing .fn{color:#d2a8ff}
|
||||
.tpl-tech-sharing .str{color:#a5d6ff}
|
||||
.tpl-tech-sharing .cmt{color:#8b949e;font-style:italic}
|
||||
.tpl-tech-sharing .num{color:#79c0ff}
|
||||
.tpl-tech-sharing .card{background:var(--surface);border:1px solid var(--border);box-shadow:none}
|
||||
.tpl-tech-sharing .card-accent{border-top:3px solid var(--accent)}
|
||||
.tpl-tech-sharing .pill{background:var(--surface-2);color:var(--text-2);border-color:var(--border)}
|
||||
.tpl-tech-sharing .pill-accent{background:rgba(126,231,135,.12);color:var(--accent);border-color:rgba(126,231,135,.35)}
|
||||
.tpl-tech-sharing .tag{display:inline-flex;align-items:center;gap:6px;padding:4px 10px;border-radius:6px;font-family:'JetBrains Mono',monospace;font-size:12px;background:var(--surface-2);border:1px solid var(--border);color:var(--text-2)}
|
||||
.tpl-tech-sharing .agenda-row{display:flex;align-items:baseline;gap:24px;padding:18px 0;border-bottom:1px dashed var(--border);font-family:'JetBrains Mono',monospace}
|
||||
.tpl-tech-sharing .agenda-row .num{color:var(--accent);flex:none;width:48px}
|
||||
.tpl-tech-sharing .agenda-row .t{color:#fff;font-size:24px;flex:1;font-family:'Inter',sans-serif;font-weight:600}
|
||||
.tpl-tech-sharing .agenda-row .d{color:var(--text-3);font-size:13px}
|
||||
.tpl-tech-sharing .speaker{display:flex;align-items:center;gap:14px;margin-top:28px}
|
||||
.tpl-tech-sharing .speaker .av{width:56px;height:56px;border-radius:50%;background:var(--grad)}
|
||||
.tpl-tech-sharing .speaker b{display:block;color:#fff;font-size:18px}
|
||||
.tpl-tech-sharing .speaker span{color:var(--text-3);font-size:13px;font-family:'JetBrains Mono',monospace}
|
||||
.tpl-tech-sharing .lede{color:var(--text-2)}
|
||||
|
||||
</style>
|
||||
<style>
|
||||
/* Static-preview fallback (runtime.js is absent — keep every slide visible) */
|
||||
.deck{height:auto;min-height:100vh;overflow:visible}
|
||||
.slide{position:relative;inset:auto;opacity:1;pointer-events:auto;transform:none;height:100vh;page-break-after:always}
|
||||
.deck-header,.deck-footer,.slide-number,.progress-bar,.notes-overlay,.overview{pointer-events:none}
|
||||
.notes{display:none!important}
|
||||
</style></head>
|
||||
<body class="tpl-tech-sharing">
|
||||
<div class="deck">
|
||||
|
||||
<!-- 1. Cover -->
|
||||
<section class="slide" data-title="Cover">
|
||||
<p class="kicker">tech-sharing / 2026-04-15</p>
|
||||
<h1 class="h1 anim-fade-up" data-anim="fade-up">Rust 异步运行时<br>到底在<span style="background:var(--grad);-webkit-background-clip:text;background-clip:text;color:transparent">调度什么</span>?</h1>
|
||||
<p class="lede mt-m">从 <span class="mono">Future::poll</span> 到 tokio 的 work-stealing,一次讲清楚。</p>
|
||||
<div class="speaker"><div class="av"></div><div><b>@lewis</b><span>platform infra · 45 min + Q&A</span></div></div>
|
||||
<div class="deck-footer"><span class="mono">#async #rust #tokio</span><span class="slide-number" data-current="1" data-total="8"></span></div>
|
||||
</section>
|
||||
|
||||
<!-- 2. Agenda -->
|
||||
<section class="slide" data-title="Agenda">
|
||||
<p class="kicker">agenda.toml</p>
|
||||
<h2 class="h2">今天的路线图</h2>
|
||||
<div class="stack mt-l">
|
||||
<div class="agenda-row"><span class="num">01</span><span class="t">Context: 为什么需要 async</span><span class="d">~5min</span></div>
|
||||
<div class="agenda-row"><span class="num">02</span><span class="t">Deep dive 1: Future & Waker</span><span class="d">~12min</span></div>
|
||||
<div class="agenda-row"><span class="num">03</span><span class="t">Deep dive 2: Tokio scheduler</span><span class="d">~15min</span></div>
|
||||
<div class="agenda-row"><span class="num">04</span><span class="t">Code: 手写一个 mini-runtime</span><span class="d">~8min</span></div>
|
||||
<div class="agenda-row"><span class="num">05</span><span class="t">Takeaways + Q&A</span><span class="d">~5min</span></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 3. Context -->
|
||||
<section class="slide" data-title="Context">
|
||||
<p class="kicker">// context</p>
|
||||
<h2 class="h2">问题:一个线程一个连接,<br>撑不住 10 万并发。</h2>
|
||||
<div class="grid g3 mt-l">
|
||||
<div class="card card-accent"><h4>Thread-per-conn</h4><p class="dim">每条连接一根 OS 线程,栈 2–8MB。10 万连接 = 几百 GB RAM。</p><span class="tag mt-s">❌ 不现实</span></div>
|
||||
<div class="card card-accent"><h4>Event loop (C)</h4><p class="dim">epoll/kqueue + 回调地狱。快,但写起来痛苦且容易出 bug。</p><span class="tag mt-s">😩 callback hell</span></div>
|
||||
<div class="card card-accent"><h4>Async / await</h4><p class="dim">看起来像同步代码,编译成状态机。一根线程跑几千任务。</p><span class="tag mt-s">✅ Rust 选这个</span></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 4. Deep dive 1 -->
|
||||
<section class="slide" data-title="Deep Dive 1">
|
||||
<p class="kicker">deep-dive · 1 / 2</p>
|
||||
<h2 class="h2">Future 其实只有一个方法。</h2>
|
||||
<div class="grid g2 mt-l" style="align-items:start">
|
||||
<div>
|
||||
<p class="lede">编译器把 <span class="mono">async fn</span> 变成一个实现了 <span class="mono">Future</span> trait 的匿名状态机。运行时只做一件事:反复 <span class="mono">poll</span> 它,直到返回 <span class="mono">Ready</span>。</p>
|
||||
<div class="mt-l">
|
||||
<span class="tag">Pending</span> <span class="tag">Ready(T)</span> <span class="tag">Waker.wake()</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="terminal">
|
||||
<div class="bar"><span class="dot"></span><span class="dot"></span><span class="dot"></span><span>future.rs</span></div>
|
||||
<pre><span class="kw">pub trait</span> <span class="fn">Future</span> {
|
||||
<span class="kw">type</span> Output;
|
||||
<span class="kw">fn</span> <span class="fn">poll</span>(
|
||||
<span class="kw">self</span>: Pin<&<span class="kw">mut Self</span>>,
|
||||
cx: &<span class="kw">mut</span> Context<<span class="str">'_</span>>,
|
||||
) -> Poll<<span class="kw">Self</span>::Output>;
|
||||
}
|
||||
|
||||
<span class="cmt">// Poll::Pending → 挂起,等 waker 唤醒</span>
|
||||
<span class="cmt">// Poll::Ready(v) → 完成,产出 v</span></pre>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 5. Deep dive 2 -->
|
||||
<section class="slide" data-title="Deep Dive 2">
|
||||
<p class="kicker">deep-dive · 2 / 2</p>
|
||||
<h2 class="h2">Tokio 是一个偷任务的小工。</h2>
|
||||
<div class="grid g2 mt-l" style="align-items:start">
|
||||
<div>
|
||||
<p class="lede">Multi-thread runtime = N 个 worker,每个 worker 有自己的本地队列。空闲的 worker 会去别人队列里"偷"任务。</p>
|
||||
<div class="stack mt-m">
|
||||
<div class="tag">✦ local queue · 256 slots</div>
|
||||
<div class="tag">✦ global injection queue</div>
|
||||
<div class="tag">✦ work-stealing @ 50% steal ratio</div>
|
||||
<div class="tag">✦ LIFO slot for cache locality</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card" style="padding:32px">
|
||||
<h4 class="mono" style="color:var(--accent-2)">scheduler tick loop</h4>
|
||||
<div class="stack mt-m" style="font-family:'JetBrains Mono',monospace;font-size:14px;line-height:1.9;color:var(--text-2)">
|
||||
<div><span style="color:var(--accent)">1.</span> pop from LIFO slot</div>
|
||||
<div><span style="color:var(--accent)">2.</span> else pop from local queue</div>
|
||||
<div><span style="color:var(--accent)">3.</span> else drain global queue (every 61 ticks)</div>
|
||||
<div><span style="color:var(--accent)">4.</span> else steal from random victim</div>
|
||||
<div><span style="color:var(--accent)">5.</span> else park the thread</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 6. Code example -->
|
||||
<section class="slide" data-title="Code">
|
||||
<p class="kicker">mini-runtime.rs · ~40 LOC</p>
|
||||
<h2 class="h2">手写一个最小 runtime。</h2>
|
||||
<div class="terminal mt-m">
|
||||
<div class="bar"><span class="dot"></span><span class="dot"></span><span class="dot"></span><span>src/main.rs</span></div>
|
||||
<pre><span class="kw">use</span> std::collections::VecDeque;
|
||||
<span class="kw">use</span> std::sync::{Arc, Mutex};
|
||||
<span class="kw">use</span> std::task::{Context, Poll, Wake, Waker};
|
||||
|
||||
<span class="kw">struct</span> Task(Mutex<Pin<Box<<span class="kw">dyn</span> Future<Output = ()> + Send>>>);
|
||||
|
||||
<span class="kw">impl</span> Wake <span class="kw">for</span> Task {
|
||||
<span class="kw">fn</span> <span class="fn">wake</span>(<span class="kw">self</span>: Arc<<span class="kw">Self</span>>) { QUEUE.lock().unwrap().push_back(<span class="kw">self</span>); }
|
||||
}
|
||||
|
||||
<span class="kw">fn</span> <span class="fn">block_on</span><F: Future<Output = ()> + Send + <span class="str">'static</span>>(fut: F) {
|
||||
<span class="fn">spawn</span>(fut);
|
||||
<span class="kw">while let Some</span>(task) = QUEUE.lock().unwrap().pop_front() {
|
||||
<span class="kw">let</span> waker = Waker::from(task.clone());
|
||||
<span class="kw">let mut</span> cx = Context::from_waker(&waker);
|
||||
<span class="kw">let mut</span> fut = task.<span class="num">0</span>.lock().unwrap();
|
||||
<span class="kw">let</span> _ = fut.as_mut().<span class="fn">poll</span>(&<span class="kw">mut</span> cx); <span class="cmt">// 就是这一行</span>
|
||||
}
|
||||
}</pre>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 7. Takeaways -->
|
||||
<section class="slide" data-title="Takeaways">
|
||||
<p class="kicker">// takeaways</p>
|
||||
<h2 class="h2">三件事带回去。</h2>
|
||||
<div class="grid g3 mt-l">
|
||||
<div class="card card-accent"><h4>1 · async 是零成本抽象</h4><p class="dim">编译成状态机,没有运行时虚表,没有 GC。</p></div>
|
||||
<div class="card card-accent"><h4>2 · Waker 是脉搏</h4><p class="dim">Future 不主动做事,运行时靠 waker 决定"什么时候再 poll"。</p></div>
|
||||
<div class="card card-accent"><h4>3 · 别在 async 里阻塞</h4><p class="dim">一行 <span class="mono">std::fs::read</span> 能让整个 worker 停摆。用 <span class="mono">spawn_blocking</span>。</p></div>
|
||||
</div>
|
||||
<p class="lede mt-l">延伸阅读:<span class="mono">tokio.rs/blog/2019-10-scheduler</span> · <span class="mono">rust-lang.github.io/async-book</span></p>
|
||||
</section>
|
||||
|
||||
<!-- 8. Q&A -->
|
||||
<section class="slide center tc" data-title="Q and A">
|
||||
<div>
|
||||
<div class="mono" style="font-size:120px;color:var(--accent);font-weight:800;letter-spacing:-.04em">?</div>
|
||||
<h2 class="h2">Questions?</h2>
|
||||
<p class="lede" style="margin:14px auto">github.com/lewis · @lewis on slack</p>
|
||||
<div class="row mt-l" style="justify-content:center">
|
||||
<span class="tag">slides: git.co/rt-deck</span>
|
||||
<span class="tag">code: git.co/mini-rt</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
</body></html>
|
||||
79
skills/html-ppt-testing-safety-alert/SKILL.md
Normal file
79
skills/html-ppt-testing-safety-alert/SKILL.md
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
---
|
||||
name: html-ppt-testing-safety-alert
|
||||
description: 红琥珀警示 deck — 顶/底 45° 红黑 hazard 条纹、红色删除线否定标题、L1/L2/L3 绿/琥珀/红 tier 卡片、圆点状态 alert box、policy-yaml 代码块(红左边框 + bad 关键词高亮)、红绿 checklist、Q1 事故堆叠柱状图。适合安全 / 风险 / 事故复盘 / 红队 / 上线前 AI 评审 / policy-as-code。
|
||||
triggers:
|
||||
- "safety alert"
|
||||
- "incident"
|
||||
- "red team"
|
||||
- "risk review"
|
||||
- "事故复盘"
|
||||
- "安全评审"
|
||||
- "policy as code"
|
||||
od:
|
||||
mode: deck
|
||||
scenario: engineering
|
||||
featured: 32
|
||||
upstream: "https://github.com/lewislulu/html-ppt-skill"
|
||||
preview:
|
||||
type: html
|
||||
entry: index.html
|
||||
design_system:
|
||||
requires: false
|
||||
speaker_notes: true
|
||||
animations: true
|
||||
example_prompt: "用 html-ppt-testing-safety-alert 模板做一份事故复盘 / 安全评审 PPT。红黑 hazard 条 + 红色删除线 + L1/L2/L3 tier 卡片 + policy-yaml 代码块。先告诉我事件时间线、根因、影响范围。"
|
||||
---
|
||||
# HTML PPT · 红琥珀警示
|
||||
|
||||
A focused entry point into the [`html-ppt`](../html-ppt/SKILL.md) master skill that lands the user directly on the **`testing-safety-alert`** full-deck template.
|
||||
|
||||
## When this card is picked
|
||||
|
||||
The Examples gallery wires "Use this prompt" to the example_prompt above. When you accept that prompt, this card is the right pick if the user wants exactly the visual identity of `testing-safety-alert` (see the upstream [full-decks catalog](../html-ppt/references/full-decks.md) for screenshots and rationale).
|
||||
|
||||
## How to author the deck
|
||||
|
||||
1. **Read the master skill first.** All authoring rules live in
|
||||
[`skills/html-ppt/SKILL.md`](../html-ppt/SKILL.md) — content/audience checklist,
|
||||
token rules, layout reuse, presenter mode, the keyboard runtime, and the
|
||||
"never put presenter-only text on the slide" rule.
|
||||
2. **Start from the matching template folder:**
|
||||
`skills/html-ppt/templates/full-decks/testing-safety-alert/` — copy `index.html` and
|
||||
`style.css` into the project, keep the `.tpl-testing-safety-alert` body class.
|
||||
3. **Bring the shared runtime with the template.** The upstream
|
||||
`index.html` links the shared CSS/JS via `../../../assets/...` because it
|
||||
sits three folders deep inside `skills/html-ppt/templates/full-decks/`.
|
||||
Once you copy `index.html` into the project, those parent-relative URLs
|
||||
no longer resolve and `base.css`, `animations.css`, and `runtime.js`
|
||||
will 404 — meaning the deck never activates and slide navigation is
|
||||
dead. Pick one of these two recipes per project:
|
||||
- **Recipe A — copy + rewrite (preferred):** copy
|
||||
`skills/html-ppt/assets/fonts.css`, `skills/html-ppt/assets/base.css`,
|
||||
`skills/html-ppt/assets/animations/animations.css`, and
|
||||
`skills/html-ppt/assets/runtime.js` into a project-local
|
||||
`assets/` (with `assets/animations/animations.css`), then rewrite the
|
||||
four `<link>`/`<script>` tags in `index.html` from
|
||||
`../../../assets/...` to the matching project-local paths
|
||||
(`assets/fonts.css`, `assets/base.css`,
|
||||
`assets/animations/animations.css`, `assets/runtime.js`).
|
||||
- **Recipe B — inline:** read the same four files and replace each
|
||||
`<link rel="stylesheet" href="../../../assets/...">` with a
|
||||
`<style>...</style>` containing the file's contents, and the
|
||||
`<script src="../../../assets/runtime.js">` with a
|
||||
`<script>...</script>` containing `runtime.js`. Yields a single
|
||||
self-contained `index.html`.
|
||||
Either way, do not ship the upstream `../../../assets/...` URLs
|
||||
verbatim into a project artifact — they only work in-tree.
|
||||
4. **Pick a theme.** Default tokens look fine; if the user wants a different
|
||||
feel, swap in any of the 36 themes from `skills/html-ppt/assets/themes/*.css`
|
||||
via `<link id="theme-link">` and let `T` cycle.
|
||||
5. **Replace demo content, not classes.** The `.tpl-testing-safety-alert` scoped CSS only
|
||||
recognises the structural classes shipped in the template — keep them.
|
||||
6. **Speaker notes go inside `<aside class="notes">` or `<div class="notes">`** — never as visible text on the slide.
|
||||
|
||||
## Attribution
|
||||
|
||||
Visual system, layouts, themes and the runtime keyboard model come from
|
||||
the upstream MIT-licensed [`lewislulu/html-ppt-skill`](https://github.com/lewislulu/html-ppt-skill). The
|
||||
LICENSE file ships at `skills/html-ppt/LICENSE`; please keep it in place when
|
||||
redistributing.
|
||||
413
skills/html-ppt-testing-safety-alert/example.html
Normal file
413
skills/html-ppt-testing-safety-alert/example.html
Normal file
|
|
@ -0,0 +1,413 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Testing Safety Alert</title>
|
||||
<style>/* html-ppt :: shared webfonts */
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@200;300;400;500;600;700;800;900&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@200;300;400;500;600;700;900&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@300;400;600;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,600;0,800;1,400&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@300;400;500;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Archivo+Black&display=swap');
|
||||
|
||||
</style>
|
||||
<style>/* html-ppt :: base.css — reset + shared tokens + layout primitives */
|
||||
/* Default tokens. Themes in assets/themes/*.css override the :root block. */
|
||||
:root {
|
||||
--bg: #ffffff;
|
||||
--bg-soft: #f7f7f8;
|
||||
--surface: #ffffff;
|
||||
--surface-2: #f2f2f4;
|
||||
--border: rgba(0,0,0,.08);
|
||||
--border-strong: rgba(0,0,0,.16);
|
||||
--text-1: #111216;
|
||||
--text-2: #55596a;
|
||||
--text-3: #8a8f9e;
|
||||
--accent: #3b6cff;
|
||||
--accent-2: #7a5cff;
|
||||
--accent-3: #ff5c8a;
|
||||
--good: #1aaf6c;
|
||||
--warn: #f5a524;
|
||||
--bad: #e0445a;
|
||||
--grad: linear-gradient(135deg,#3b6cff,#7a5cff 55%,#ff5c8a);
|
||||
--grad-soft: linear-gradient(135deg,#eef2ff,#f5ecff 55%,#ffeef5);
|
||||
--radius: 18px;
|
||||
--radius-sm: 12px;
|
||||
--radius-lg: 26px;
|
||||
--shadow: 0 10px 30px rgba(18,24,40,.08), 0 2px 6px rgba(18,24,40,.04);
|
||||
--shadow-lg: 0 24px 60px rgba(18,24,40,.14), 0 6px 16px rgba(18,24,40,.06);
|
||||
--font-sans: 'Inter','Noto Sans SC',-apple-system,BlinkMacSystemFont,Helvetica,Arial,sans-serif;
|
||||
--font-serif: 'Playfair Display','Noto Serif SC',Georgia,serif;
|
||||
--font-mono: 'JetBrains Mono','IBM Plex Mono',SFMono-Regular,Menlo,monospace;
|
||||
--font-display: var(--font-sans);
|
||||
--letter-tight: -.03em;
|
||||
--letter-normal: -.01em;
|
||||
--ease: cubic-bezier(.4,0,.2,1);
|
||||
}
|
||||
|
||||
*,*::before,*::after{box-sizing:border-box}
|
||||
html,body{margin:0;padding:0;background:var(--bg);color:var(--text-1);
|
||||
font-family:var(--font-sans);font-weight:400;line-height:1.6;
|
||||
-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;
|
||||
letter-spacing:var(--letter-normal)}
|
||||
img,svg,video{max-width:100%;display:block}
|
||||
a{color:var(--accent);text-decoration:none}
|
||||
a:hover{text-decoration:underline}
|
||||
code,kbd,pre,samp{font-family:var(--font-mono)}
|
||||
|
||||
/* ================= SLIDE SYSTEM ================= */
|
||||
.deck{position:relative;width:100vw;height:100vh;overflow:hidden;background:var(--bg)}
|
||||
.slide{
|
||||
position:absolute;inset:0;
|
||||
display:flex;flex-direction:column;justify-content:center;
|
||||
padding:72px 96px;
|
||||
box-sizing:border-box;
|
||||
opacity:0;pointer-events:none;
|
||||
transition:opacity .5s var(--ease), transform .5s var(--ease);
|
||||
transform:translateX(30px);
|
||||
overflow:hidden;
|
||||
}
|
||||
.slide.is-active{opacity:1;pointer-events:auto;transform:translateX(0);z-index:2}
|
||||
.slide.is-prev{transform:translateX(-30px)}
|
||||
|
||||
/* single-page standalone (used when a layout file is opened directly) */
|
||||
body.single .slide{position:relative;width:100vw;height:100vh;opacity:1;transform:none;pointer-events:auto}
|
||||
|
||||
/* ================= TYPOGRAPHY ================= */
|
||||
.eyebrow{font-size:13px;font-weight:500;letter-spacing:.16em;text-transform:uppercase;color:var(--text-3)}
|
||||
.kicker{font-size:14px;font-weight:600;color:var(--accent);letter-spacing:.08em;text-transform:uppercase}
|
||||
h1.title,.h1{font-family:var(--font-display);font-size:72px;line-height:1.05;font-weight:800;letter-spacing:var(--letter-tight);margin:0 0 18px;color:var(--text-1)}
|
||||
h2.title,.h2{font-family:var(--font-display);font-size:54px;line-height:1.1;font-weight:700;letter-spacing:var(--letter-tight);margin:0 0 14px}
|
||||
h3,.h3{font-size:32px;line-height:1.2;font-weight:600;letter-spacing:var(--letter-normal);margin:0 0 10px}
|
||||
h4,.h4{font-size:22px;line-height:1.3;font-weight:600;margin:0 0 8px}
|
||||
.lede{font-size:22px;line-height:1.55;color:var(--text-2);font-weight:300;max-width:62ch}
|
||||
.dim{color:var(--text-2)}
|
||||
.dim2{color:var(--text-3)}
|
||||
.mono{font-family:var(--font-mono)}
|
||||
.serif{font-family:var(--font-serif)}
|
||||
.gradient-text{background:var(--grad);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;color:transparent}
|
||||
|
||||
/* ================= LAYOUT PRIMITIVES ================= */
|
||||
.stack>*+*{margin-top:14px}
|
||||
.row{display:flex;gap:24px;align-items:center}
|
||||
.row.wrap{flex-wrap:wrap}
|
||||
.grid{display:grid;gap:24px}
|
||||
.g2{grid-template-columns:repeat(2,1fr)}
|
||||
.g3{grid-template-columns:repeat(3,1fr)}
|
||||
.g4{grid-template-columns:repeat(4,1fr)}
|
||||
.center{display:flex;align-items:center;justify-content:center;text-align:center}
|
||||
.fill{flex:1}
|
||||
.sp-t{padding-top:24px}.sp-b{padding-bottom:24px}
|
||||
.mt-s{margin-top:8px}.mt-m{margin-top:18px}.mt-l{margin-top:32px}
|
||||
.mb-s{margin-bottom:8px}.mb-m{margin-bottom:18px}.mb-l{margin-bottom:32px}
|
||||
|
||||
/* ================= CARDS ================= */
|
||||
.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);
|
||||
padding:26px 28px;box-shadow:var(--shadow);position:relative;overflow:hidden}
|
||||
.card-soft{background:var(--surface-2);border:1px solid var(--border)}
|
||||
.card-outline{background:transparent;border:1.5px solid var(--border-strong);box-shadow:none}
|
||||
.card-accent{background:var(--surface);border-top:3px solid var(--accent)}
|
||||
.card-hover{transition:transform .3s var(--ease),box-shadow .3s var(--ease)}
|
||||
.card-hover:hover{transform:translateY(-4px);box-shadow:var(--shadow-lg)}
|
||||
|
||||
.pill{display:inline-block;padding:4px 12px;border-radius:999px;font-size:12px;font-weight:500;
|
||||
background:var(--surface-2);color:var(--text-2);border:1px solid var(--border)}
|
||||
.pill-accent{background:color-mix(in srgb,var(--accent) 12%,transparent);color:var(--accent);border-color:color-mix(in srgb,var(--accent) 28%,transparent)}
|
||||
|
||||
/* ================= BARS / DIVIDERS ================= */
|
||||
.divider{height:1px;background:var(--border);width:100%}
|
||||
.divider-accent{height:3px;width:72px;background:var(--accent);border-radius:2px}
|
||||
|
||||
/* ================= CHROME (header/footer/progress) ================= */
|
||||
.deck-header{position:absolute;top:24px;left:40px;right:40px;display:flex;align-items:center;justify-content:space-between;
|
||||
font-size:12px;color:var(--text-3);letter-spacing:.12em;text-transform:uppercase;z-index:10;pointer-events:none}
|
||||
.deck-footer{position:absolute;bottom:24px;left:40px;right:40px;display:flex;align-items:center;justify-content:space-between;
|
||||
font-size:12px;color:var(--text-3);z-index:10;pointer-events:none}
|
||||
.slide-number::before{content:attr(data-current)}
|
||||
.slide-number::after{content:" / " attr(data-total)}
|
||||
.progress-bar{position:fixed;left:0;right:0;bottom:0;height:3px;background:transparent;z-index:20}
|
||||
.progress-bar > span{display:block;height:100%;width:0;background:var(--accent);transition:width .3s var(--ease)}
|
||||
|
||||
/* ================= PRESENTER / OVERVIEW ================= */
|
||||
.notes{display:none!important}
|
||||
.notes-overlay{position:fixed;inset:auto 0 0 0;max-height:42vh;background:rgba(20,22,30,.95);color:#e8ebf4;
|
||||
padding:20px 32px;font-size:16px;line-height:1.6;border-top:1px solid rgba(255,255,255,.1);transform:translateY(100%);
|
||||
transition:transform .3s var(--ease);z-index:40;overflow:auto;font-family:var(--font-sans)}
|
||||
.notes-overlay.open{transform:translateY(0)}
|
||||
.overview{position:fixed;inset:0;background:rgba(10,12,18,.92);backdrop-filter:blur(12px);z-index:50;
|
||||
display:none;padding:40px;overflow:auto}
|
||||
.overview.open{display:grid;grid-template-columns:repeat(4,1fr);gap:20px;align-content:start}
|
||||
.overview .thumb{background:var(--surface);border:1px solid var(--border);border-radius:12px;
|
||||
aspect-ratio:16/9;overflow:hidden;cursor:pointer;position:relative;color:var(--text-1);padding:16px;
|
||||
font-size:11px;transition:transform .2s var(--ease)}
|
||||
.overview .thumb:hover{transform:scale(1.04)}
|
||||
.overview .thumb .n{position:absolute;top:8px;left:10px;font-weight:700;font-size:14px;color:var(--text-3)}
|
||||
.overview .thumb .t{position:absolute;bottom:10px;left:14px;right:14px;font-weight:600;color:var(--text-1)}
|
||||
|
||||
/* ================= PRESENTER VIEW ================= */
|
||||
/* Presenter view opens in a separate popup window (S key).
|
||||
* All presenter styles are self-contained in the popup HTML generated by runtime.js.
|
||||
* The audience window (this file) is NOT affected — it stays as normal deck view.
|
||||
* Only the .notes class below is needed to hide speaker notes from audience. */
|
||||
|
||||
/* ================= UTILITY ================= */
|
||||
.hidden{display:none!important}
|
||||
.nowrap{white-space:nowrap}
|
||||
.tr{text-align:right}.tc{text-align:center}.tl{text-align:left}
|
||||
.uppercase{text-transform:uppercase;letter-spacing:.12em}
|
||||
|
||||
/* ================= PRINT ================= */
|
||||
@media print{
|
||||
.slide{position:relative;opacity:1!important;transform:none!important;page-break-after:always;height:100vh}
|
||||
.deck-header,.deck-footer,.progress-bar,.notes-overlay,.overview{display:none!important}
|
||||
}
|
||||
|
||||
</style>
|
||||
<style>/* testing-safety-alert — 红/琥珀 警示风 · 白底高对比 */
|
||||
.tpl-testing-safety-alert{
|
||||
--ts-bg:#fffaf7;
|
||||
--ts-ink:#14141a;
|
||||
--ts-ink2:#4a4955;
|
||||
--ts-muted:#8a8892;
|
||||
--ts-line:rgba(20,20,26,.08);
|
||||
--ts-red:#e0314a;
|
||||
--ts-red-soft:#ffecee;
|
||||
--ts-amber:#d97706;
|
||||
--ts-amber-soft:#fff5e6;
|
||||
--ts-green:#067647;
|
||||
--ts-green-soft:#e8f8ee;
|
||||
background:var(--ts-bg);
|
||||
color:var(--ts-ink);
|
||||
font-family:'Inter','Noto Sans SC','PingFang SC',-apple-system,sans-serif;
|
||||
}
|
||||
.tpl-testing-safety-alert .slide{background:var(--ts-bg);color:var(--ts-ink);padding:64px 84px}
|
||||
.tpl-testing-safety-alert .ts-stripe{position:absolute;top:0;left:0;right:0;height:14px;background:repeating-linear-gradient(45deg,var(--ts-red) 0 18px,#111318 18px 36px)}
|
||||
.tpl-testing-safety-alert .ts-stripe-b{position:absolute;bottom:0;left:0;right:0;height:6px;background:repeating-linear-gradient(45deg,var(--ts-red) 0 10px,#111318 10px 20px);opacity:.6}
|
||||
.tpl-testing-safety-alert .ts-chrome{display:flex;justify-content:space-between;align-items:center;margin:22px 0 16px}
|
||||
.tpl-testing-safety-alert .ts-alert-tag{display:inline-flex;align-items:center;gap:10px;padding:8px 18px;border-radius:10px;font-size:13px;font-weight:800;letter-spacing:.12em;text-transform:uppercase;background:var(--ts-red);color:#fff;box-shadow:0 6px 18px rgba(224,49,74,.28)}
|
||||
.tpl-testing-safety-alert .ts-alert-tag::before{content:'⚠';font-size:16px}
|
||||
.tpl-testing-safety-alert .ts-alert-tag.amber{background:var(--ts-amber);box-shadow:0 6px 18px rgba(217,119,6,.25)}
|
||||
.tpl-testing-safety-alert .ts-alert-tag.green{background:var(--ts-green);box-shadow:0 6px 18px rgba(6,118,71,.22)}
|
||||
.tpl-testing-safety-alert .ts-alert-tag.green::before{content:'✓'}
|
||||
.tpl-testing-safety-alert .ts-page{font-size:13px;color:var(--ts-muted);letter-spacing:.15em;font-weight:700}
|
||||
.tpl-testing-safety-alert .ts-kicker{font-size:15px;font-weight:700;color:var(--ts-red);letter-spacing:.06em;margin-bottom:10px;text-transform:uppercase}
|
||||
.tpl-testing-safety-alert .ts-h1{font-size:88px;font-weight:900;line-height:1.04;letter-spacing:-2px;margin:10px 0 16px;color:var(--ts-ink)}
|
||||
.tpl-testing-safety-alert .ts-h1 .red{color:var(--ts-red)}
|
||||
.tpl-testing-safety-alert .ts-h1 .strike{position:relative;display:inline-block}
|
||||
.tpl-testing-safety-alert .ts-h1 .strike::after{content:'';position:absolute;left:-4%;right:-4%;top:50%;height:10px;background:var(--ts-red);transform:skewX(-12deg);opacity:.85}
|
||||
.tpl-testing-safety-alert .ts-h2{font-size:54px;font-weight:900;line-height:1.1;letter-spacing:-1px;margin:0 0 14px}
|
||||
.tpl-testing-safety-alert .ts-sub{font-size:22px;line-height:1.5;color:var(--ts-ink2);max-width:880px;margin-top:10px}
|
||||
.tpl-testing-safety-alert .ts-highlight-red{display:inline-block;padding:4px 14px;background:var(--ts-red);color:#fff;border-radius:8px;font-weight:800}
|
||||
.tpl-testing-safety-alert .ts-highlight-amber{display:inline-block;padding:4px 14px;background:var(--ts-amber-soft);color:var(--ts-amber);border-radius:8px;font-weight:800;border:1px solid rgba(217,119,6,.2)}
|
||||
.tpl-testing-safety-alert .ts-highlight-green{display:inline-block;padding:4px 14px;background:var(--ts-green-soft);color:var(--ts-green);border-radius:8px;font-weight:800;border:1px solid rgba(6,118,71,.2)}
|
||||
.tpl-testing-safety-alert .ts-alert-box{border:2px solid var(--ts-red);border-radius:18px;padding:26px 30px;background:linear-gradient(180deg,#fff 0%,var(--ts-red-soft) 100%);box-shadow:0 14px 36px rgba(224,49,74,.14);margin-top:24px;position:relative}
|
||||
.tpl-testing-safety-alert .ts-alert-box::before{content:'';position:absolute;top:-11px;left:24px;width:22px;height:22px;background:var(--ts-red);border-radius:50%;box-shadow:0 0 0 6px rgba(224,49,74,.2)}
|
||||
.tpl-testing-safety-alert .ts-alert-box.amber{border-color:var(--ts-amber);background:linear-gradient(180deg,#fff 0%,var(--ts-amber-soft) 100%);box-shadow:0 14px 36px rgba(217,119,6,.14)}
|
||||
.tpl-testing-safety-alert .ts-alert-box.amber::before{background:var(--ts-amber);box-shadow:0 0 0 6px rgba(217,119,6,.2)}
|
||||
.tpl-testing-safety-alert .ts-alert-box.green{border-color:var(--ts-green);background:linear-gradient(180deg,#fff 0%,var(--ts-green-soft) 100%);box-shadow:0 14px 36px rgba(6,118,71,.14)}
|
||||
.tpl-testing-safety-alert .ts-alert-box.green::before{background:var(--ts-green);box-shadow:0 0 0 6px rgba(6,118,71,.2)}
|
||||
.tpl-testing-safety-alert .ts-alert-box h3{font-size:34px;font-weight:900;margin:0 0 10px}
|
||||
.tpl-testing-safety-alert .ts-alert-box p{font-size:17px;line-height:1.6;color:var(--ts-ink2);margin:0}
|
||||
.tpl-testing-safety-alert .ts-grid-2{display:grid;grid-template-columns:1fr 1fr;gap:20px;margin-top:20px}
|
||||
.tpl-testing-safety-alert .ts-grid-3{display:grid;grid-template-columns:1fr 1fr 1fr;gap:16px;margin-top:20px}
|
||||
.tpl-testing-safety-alert .ts-card{border:1px solid var(--ts-line);border-radius:16px;padding:22px 24px;background:#fff;box-shadow:0 6px 20px rgba(17,19,24,.04)}
|
||||
.tpl-testing-safety-alert .ts-card .lbl{font-size:12px;font-weight:800;letter-spacing:.12em;text-transform:uppercase;color:var(--ts-muted);margin-bottom:8px}
|
||||
.tpl-testing-safety-alert .ts-card h4{font-size:26px;font-weight:900;line-height:1.2;margin-bottom:8px}
|
||||
.tpl-testing-safety-alert .ts-card p{font-size:14px;color:var(--ts-ink2);line-height:1.55}
|
||||
.tpl-testing-safety-alert .ts-checklist{display:flex;flex-direction:column;gap:12px;margin-top:20px;max-width:880px}
|
||||
.tpl-testing-safety-alert .ts-check{display:flex;gap:16px;align-items:flex-start;padding:16px 20px;border:1px solid var(--ts-line);border-radius:14px;background:#fff}
|
||||
.tpl-testing-safety-alert .ts-check .box{flex:0 0 32px;height:32px;border-radius:8px;border:2px solid var(--ts-red);display:grid;place-items:center;font-weight:900;color:var(--ts-red);background:var(--ts-red-soft)}
|
||||
.tpl-testing-safety-alert .ts-check.ok .box{border-color:var(--ts-green);color:var(--ts-green);background:var(--ts-green-soft)}
|
||||
.tpl-testing-safety-alert .ts-check .txt{font-size:18px;line-height:1.5;font-weight:600}
|
||||
.tpl-testing-safety-alert .ts-codebox{background:#141418;color:#fff5ea;border-radius:14px;padding:22px 26px;font-family:'JetBrains Mono',monospace;font-size:14px;line-height:1.85;margin-top:20px;border-left:6px solid var(--ts-red)}
|
||||
.tpl-testing-safety-alert .ts-codebox .cm{color:#7a756d}
|
||||
.tpl-testing-safety-alert .ts-codebox .kw{color:#ffb38a}
|
||||
.tpl-testing-safety-alert .ts-codebox .st{color:#b3e6c2}
|
||||
.tpl-testing-safety-alert .ts-codebox .bad{color:#ff9aa8;font-weight:700}
|
||||
.tpl-testing-safety-alert .ts-footer{position:absolute;left:84px;right:84px;bottom:36px;display:flex;justify-content:space-between;font-size:12px;color:var(--ts-muted);letter-spacing:.1em}
|
||||
|
||||
</style>
|
||||
<style>
|
||||
/* Static-preview fallback (runtime.js is absent — keep every slide visible) */
|
||||
.deck{height:auto;min-height:100vh;overflow:visible}
|
||||
.slide{position:relative;inset:auto;opacity:1;pointer-events:auto;transform:none;height:100vh;page-break-after:always}
|
||||
.deck-header,.deck-footer,.slide-number,.progress-bar,.notes-overlay,.overview{pointer-events:none}
|
||||
.notes{display:none!important}
|
||||
</style></head>
|
||||
<body class="tpl-testing-safety-alert">
|
||||
<div class="deck">
|
||||
|
||||
<!-- 1. COVER -->
|
||||
<section class="slide is-active">
|
||||
<div class="ts-stripe"></div>
|
||||
<div class="ts-chrome"><span class="ts-alert-tag">ai safety · 高优先级</span><span class="ts-page">01 / 08</span></div>
|
||||
<div class="ts-kicker">2026 年最重要的一条判断</div>
|
||||
<h1 class="ts-h1">别再追问<br><span class="strike">AI 会不会干活</span><br>开始问:<span class="red">它出事谁负责</span></h1>
|
||||
<p class="ts-sub">AI 出错的代价,不再是一次 bad response 这么简单 —— 它可能一次性写 300 份工单、提 80 个 PR、发 5000 封邮件。</p>
|
||||
<div class="ts-alert-box">
|
||||
<h3>风险已经规模化</h3>
|
||||
<p>「做错」成本 × N;「做对」收益 × N。<br>这就是为什么 <b>测试、验收、安全、风控</b> 会变成未来 3 年最贵的能力。</p>
|
||||
</div>
|
||||
<div class="ts-stripe-b"></div>
|
||||
<div class="ts-footer"><span>AI SAFETY BRIEF · LEWIS · 2026.04</span><span>01 / 08</span></div>
|
||||
</section>
|
||||
|
||||
<!-- 2. SECTION -->
|
||||
<section class="slide">
|
||||
<div class="ts-stripe"></div>
|
||||
<div class="ts-chrome"><span class="ts-alert-tag amber">section · risk 分级</span><span class="ts-page">02 / 08</span></div>
|
||||
<div style="margin:auto 0">
|
||||
<div class="ts-kicker">Chapter One</div>
|
||||
<h1 class="ts-h1" style="font-size:130px">先分 <span class="red">等级</span></h1>
|
||||
<p class="ts-sub" style="font-size:28px">不是所有 AI 行为都同等危险。<br>先把「可撤销」和「不可撤销」分开,再谈流程。</p>
|
||||
</div>
|
||||
<div class="ts-stripe-b"></div>
|
||||
<div class="ts-footer"><span>section · level taxonomy</span><span>02 / 08</span></div>
|
||||
</section>
|
||||
|
||||
<!-- 3. CONTENT risk levels -->
|
||||
<section class="slide">
|
||||
<div class="ts-stripe"></div>
|
||||
<div class="ts-chrome"><span class="ts-alert-tag">风险分级 · 3 levels</span><span class="ts-page">03 / 08</span></div>
|
||||
<h2 class="ts-h2">三档风险,三种处理</h2>
|
||||
<div class="ts-grid-3">
|
||||
<div class="ts-card" style="border-top:4px solid var(--ts-green)"><div class="lbl">L1 · 绿色</div><h4>可撤销</h4><p>写 draft、生成图片、起草文档。<br>错了 Ctrl+Z,零代价。<br><b style="color:var(--ts-green)">策略:放开跑</b></p></div>
|
||||
<div class="ts-card" style="border-top:4px solid var(--ts-amber)"><div class="lbl">L2 · 琥珀</div><h4>半可撤销</h4><p>发 draft 邮件、提 PR、改 staging 数据。<br>错了要道歉 / 回滚。<br><b style="color:var(--ts-amber)">策略:人工复核</b></p></div>
|
||||
<div class="ts-card" style="border-top:4px solid var(--ts-red)"><div class="lbl">L3 · 红色</div><h4>不可撤销</h4><p>发真实邮件、付款、删库、删 prod 数据。<br>错了就真错了。<br><b style="color:var(--ts-red)">策略:硬卡 + 双人审</b></p></div>
|
||||
</div>
|
||||
<div class="ts-alert-box amber">
|
||||
<h3>绝不要让 agent 自己升级</h3>
|
||||
<p>L1 的任务不能自己变成 L2。授权必须是显式的、可撤销的、带过期时间的。</p>
|
||||
</div>
|
||||
<div class="ts-stripe-b"></div>
|
||||
<div class="ts-footer"><span>risk · 3 levels</span><span>03 / 08</span></div>
|
||||
</section>
|
||||
|
||||
<!-- 4. CODE -->
|
||||
<section class="slide">
|
||||
<div class="ts-stripe"></div>
|
||||
<div class="ts-chrome"><span class="ts-alert-tag">policy as code</span><span class="ts-page">04 / 08</span></div>
|
||||
<div class="ts-kicker">别用文档管规则 · 用代码管规则</div>
|
||||
<h2 class="ts-h2">三十行 YAML,<br><span class="ts-highlight-red">红线硬卡</span></h2>
|
||||
<pre class="ts-codebox"><span class="cm"># safety-policy.yaml · compiled → runtime guard</span>
|
||||
<span class="kw">level_1_allow</span>:
|
||||
- tools: [<span class="st">write_draft</span>, <span class="st">generate_image</span>, <span class="st">read_docs</span>]
|
||||
|
||||
<span class="kw">level_2_require_review</span>:
|
||||
- tools: [<span class="st">send_email_draft</span>, <span class="st">open_pr</span>, <span class="st">write_staging_db</span>]
|
||||
reviewer: <span class="st">human</span>
|
||||
|
||||
<span class="kw">level_3_hard_block</span>:
|
||||
- tools: [<span class="st">send_real_email</span>, <span class="st">transfer_money</span>, <span class="st">delete_prod</span>]
|
||||
unless: <span class="st">two_human_sign_off AND within_24h</span>
|
||||
|
||||
<span class="bad">forbidden_always</span>:
|
||||
- <span class="bad">"rm -rf /"</span>
|
||||
- <span class="bad">"drop table"</span>
|
||||
- <span class="bad">"force push origin main"</span></pre>
|
||||
<div class="ts-stripe-b"></div>
|
||||
<div class="ts-footer"><span>policy · yaml-as-guard</span><span>04 / 08</span></div>
|
||||
</section>
|
||||
|
||||
<!-- 5. CHART -->
|
||||
<section class="slide">
|
||||
<div class="ts-stripe"></div>
|
||||
<div class="ts-chrome"><span class="ts-alert-tag amber">incident report · q1</span><span class="ts-page">05 / 08</span></div>
|
||||
<h2 class="ts-h2">我们 Q1 的 <span class="red">12 起 AI 事故</span></h2>
|
||||
<p class="ts-sub">幸好全部捕获在 staging。但每一起都能上生产。</p>
|
||||
<svg viewBox="0 0 1040 360" style="width:100%;max-width:1040px;margin-top:18px" xmlns="http://www.w3.org/2000/svg">
|
||||
<g font-family="Inter,sans-serif" font-size="14" fill="#4a4955">
|
||||
<line x1="70" y1="320" x2="1000" y2="320" stroke="#eaecf3" stroke-width="2"/>
|
||||
<!-- month columns: Jan Feb Mar, L1/L2/L3 stacked -->
|
||||
<g transform="translate(120,0)">
|
||||
<rect x="0" y="220" width="60" height="100" fill="#067647"/>
|
||||
<rect x="0" y="160" width="60" height="60" fill="#d97706"/>
|
||||
<rect x="0" y="130" width="60" height="30" fill="#e0314a"/>
|
||||
<text x="30" y="345" text-anchor="middle" font-weight="700">Jan</text>
|
||||
<text x="30" y="120" text-anchor="middle" font-weight="800" fill="#14141a">5</text>
|
||||
</g>
|
||||
<g transform="translate(320,0)">
|
||||
<rect x="0" y="240" width="60" height="80" fill="#067647"/>
|
||||
<rect x="0" y="200" width="60" height="40" fill="#d97706"/>
|
||||
<rect x="0" y="180" width="60" height="20" fill="#e0314a"/>
|
||||
<text x="30" y="345" text-anchor="middle" font-weight="700">Feb</text>
|
||||
<text x="30" y="170" text-anchor="middle" font-weight="800" fill="#14141a">3</text>
|
||||
</g>
|
||||
<g transform="translate(520,0)">
|
||||
<rect x="0" y="250" width="60" height="70" fill="#067647"/>
|
||||
<rect x="0" y="220" width="60" height="30" fill="#d97706"/>
|
||||
<rect x="0" y="210" width="60" height="10" fill="#e0314a"/>
|
||||
<text x="30" y="345" text-anchor="middle" font-weight="700">Mar</text>
|
||||
<text x="30" y="200" text-anchor="middle" font-weight="800" fill="#14141a">4</text>
|
||||
</g>
|
||||
<!-- legend -->
|
||||
<g transform="translate(720,60)">
|
||||
<rect x="0" y="0" width="16" height="16" fill="#e0314a"/><text x="24" y="13" font-weight="700">L3 不可撤销 (3)</text>
|
||||
<rect x="0" y="26" width="16" height="16" fill="#d97706"/><text x="24" y="39" font-weight="700">L2 需复核 (4)</text>
|
||||
<rect x="0" y="52" width="16" height="16" fill="#067647"/><text x="24" y="65" font-weight="700">L1 可恢复 (5)</text>
|
||||
<text x="0" y="100" font-size="13" fill="#8a8892">全部被 safety-policy 在 runtime 拦下,</text>
|
||||
<text x="0" y="118" font-size="13" fill="#8a8892">未进 prod。但 3 起 L3 非常惊险。</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
<div class="ts-stripe-b"></div>
|
||||
<div class="ts-footer"><span>incident · q1 summary</span><span>05 / 08</span></div>
|
||||
</section>
|
||||
|
||||
<!-- 6. CHECKLIST -->
|
||||
<section class="slide">
|
||||
<div class="ts-stripe"></div>
|
||||
<div class="ts-chrome"><span class="ts-alert-tag green">red-team checklist</span><span class="ts-page">06 / 08</span></div>
|
||||
<h2 class="ts-h2">上线前 <span class="red">必过 7 道题</span></h2>
|
||||
<div class="ts-checklist">
|
||||
<div class="ts-check ok"><div class="box">✓</div><div class="txt">它能删除东西吗?有人类 review 吗?能 60 秒内回滚吗?</div></div>
|
||||
<div class="ts-check ok"><div class="box">✓</div><div class="txt">它的 prompt 注入能让它越权吗?(跑过红队提示词)</div></div>
|
||||
<div class="ts-check"><div class="box">!</div><div class="txt">它处理 PII 吗?日志里是不是也有 PII?</div></div>
|
||||
<div class="ts-check ok"><div class="box">✓</div><div class="txt">上下游失败时,它会不会开始乱改其他资源?</div></div>
|
||||
<div class="ts-check"><div class="box">!</div><div class="txt">并发 100 个 agent 一起跑会不会死锁?</div></div>
|
||||
<div class="ts-check ok"><div class="box">✓</div><div class="txt">错了能不能 <b>立刻</b> 停?(kill switch 能 2 秒内生效吗)</div></div>
|
||||
<div class="ts-check"><div class="box">!</div><div class="txt">出事时有没有人值班?值班手册有没有 agent 专属章节?</div></div>
|
||||
</div>
|
||||
<div class="ts-stripe-b"></div>
|
||||
<div class="ts-footer"><span>checklist · pre-launch</span><span>06 / 08</span></div>
|
||||
</section>
|
||||
|
||||
<!-- 7. CTA -->
|
||||
<section class="slide">
|
||||
<div class="ts-stripe"></div>
|
||||
<div class="ts-chrome"><span class="ts-alert-tag green">今晚就能动</span><span class="ts-page">07 / 08</span></div>
|
||||
<h2 class="ts-h2">今晚先做 <span class="ts-highlight-red">三件事</span></h2>
|
||||
<div class="ts-grid-3">
|
||||
<div class="ts-card"><div class="lbl">1 · 分级</div><h4>给你的 agent<br>写 L1/L2/L3</h4><p>把所有工具列出来,标上等级。不标的一律按 L3。</p></div>
|
||||
<div class="ts-card"><div class="lbl">2 · 写 policy</div><h4>policy.yaml<br>接 runtime</h4><p>不要信 prompt 里的 "be careful",要信执行层的硬卡。</p></div>
|
||||
<div class="ts-card"><div class="lbl">3 · kill switch</div><h4>红按钮<br>能在 2 秒内停</h4><p>CTO / on-call 都得知道怎么按。演练一次。</p></div>
|
||||
</div>
|
||||
<div class="ts-alert-box green">
|
||||
<h3>真正的安全不是 prompt,是流程</h3>
|
||||
<p>prompt 会被注入,流程不会。—— 把保护放在不可被说服的一层。</p>
|
||||
</div>
|
||||
<div class="ts-stripe-b"></div>
|
||||
<div class="ts-footer"><span>cta · tonight</span><span>07 / 08</span></div>
|
||||
</section>
|
||||
|
||||
<!-- 8. THANKS -->
|
||||
<section class="slide">
|
||||
<div class="ts-stripe"></div>
|
||||
<div class="ts-chrome"><span class="ts-alert-tag amber">please stay safe</span><span class="ts-page">08 / 08</span></div>
|
||||
<div style="margin:auto 0">
|
||||
<div class="ts-kicker">end of brief</div>
|
||||
<h1 class="ts-h1" style="font-size:140px">谢谢 <span class="red">·</span> thanks</h1>
|
||||
<p class="ts-sub" style="font-size:24px">policy.yaml 模板、红队 prompt 清单、事故复盘模板 —— 评论区扣「安全」。</p>
|
||||
</div>
|
||||
<div class="ts-stripe-b"></div>
|
||||
<div class="ts-footer"><span>end of brief</span><span>08 / 08</span></div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
78
skills/html-ppt-weekly-report/SKILL.md
Normal file
78
skills/html-ppt-weekly-report/SKILL.md
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
---
|
||||
name: html-ppt-weekly-report
|
||||
description: Team weekly / status-update deck — corporate clarity, 8-cell KPI grid, shipped list, 8-week bar chart, next-week table. Use for 周报, business reviews, team status updates, and exec dashboards.
|
||||
triggers:
|
||||
- "weekly report"
|
||||
- "周报"
|
||||
- "status update"
|
||||
- "team report"
|
||||
- "business review"
|
||||
- "wbr"
|
||||
od:
|
||||
mode: deck
|
||||
scenario: operations
|
||||
featured: 23
|
||||
upstream: "https://github.com/lewislulu/html-ppt-skill"
|
||||
preview:
|
||||
type: html
|
||||
entry: index.html
|
||||
design_system:
|
||||
requires: false
|
||||
speaker_notes: true
|
||||
animations: true
|
||||
example_prompt: "用 html-ppt-weekly-report 模板生成一份周报(7 页)。先问我四件事:本周时间范围、3-5 个核心 KPI 数字、本周已发布 / 已完成的事项、下周计划与风险。然后用模板填好 8 周柱状图和下周表格。"
|
||||
---
|
||||
# HTML PPT · Weekly Report
|
||||
|
||||
A focused entry point into the [`html-ppt`](../html-ppt/SKILL.md) master skill that lands the user directly on the **`weekly-report`** full-deck template.
|
||||
|
||||
## When this card is picked
|
||||
|
||||
The Examples gallery wires "Use this prompt" to the example_prompt above. When you accept that prompt, this card is the right pick if the user wants exactly the visual identity of `weekly-report` (see the upstream [full-decks catalog](../html-ppt/references/full-decks.md) for screenshots and rationale).
|
||||
|
||||
## How to author the deck
|
||||
|
||||
1. **Read the master skill first.** All authoring rules live in
|
||||
[`skills/html-ppt/SKILL.md`](../html-ppt/SKILL.md) — content/audience checklist,
|
||||
token rules, layout reuse, presenter mode, the keyboard runtime, and the
|
||||
"never put presenter-only text on the slide" rule.
|
||||
2. **Start from the matching template folder:**
|
||||
`skills/html-ppt/templates/full-decks/weekly-report/` — copy `index.html` and
|
||||
`style.css` into the project, keep the `.tpl-weekly-report` body class.
|
||||
3. **Bring the shared runtime with the template.** The upstream
|
||||
`index.html` links the shared CSS/JS via `../../../assets/...` because it
|
||||
sits three folders deep inside `skills/html-ppt/templates/full-decks/`.
|
||||
Once you copy `index.html` into the project, those parent-relative URLs
|
||||
no longer resolve and `base.css`, `animations.css`, and `runtime.js`
|
||||
will 404 — meaning the deck never activates and slide navigation is
|
||||
dead. Pick one of these two recipes per project:
|
||||
- **Recipe A — copy + rewrite (preferred):** copy
|
||||
`skills/html-ppt/assets/fonts.css`, `skills/html-ppt/assets/base.css`,
|
||||
`skills/html-ppt/assets/animations/animations.css`, and
|
||||
`skills/html-ppt/assets/runtime.js` into a project-local
|
||||
`assets/` (with `assets/animations/animations.css`), then rewrite the
|
||||
four `<link>`/`<script>` tags in `index.html` from
|
||||
`../../../assets/...` to the matching project-local paths
|
||||
(`assets/fonts.css`, `assets/base.css`,
|
||||
`assets/animations/animations.css`, `assets/runtime.js`).
|
||||
- **Recipe B — inline:** read the same four files and replace each
|
||||
`<link rel="stylesheet" href="../../../assets/...">` with a
|
||||
`<style>...</style>` containing the file's contents, and the
|
||||
`<script src="../../../assets/runtime.js">` with a
|
||||
`<script>...</script>` containing `runtime.js`. Yields a single
|
||||
self-contained `index.html`.
|
||||
Either way, do not ship the upstream `../../../assets/...` URLs
|
||||
verbatim into a project artifact — they only work in-tree.
|
||||
4. **Pick a theme.** Default tokens look fine; if the user wants a different
|
||||
feel, swap in any of the 36 themes from `skills/html-ppt/assets/themes/*.css`
|
||||
via `<link id="theme-link">` and let `T` cycle.
|
||||
5. **Replace demo content, not classes.** The `.tpl-weekly-report` scoped CSS only
|
||||
recognises the structural classes shipped in the template — keep them.
|
||||
6. **Speaker notes go inside `<aside class="notes">` or `<div class="notes">`** — never as visible text on the slide.
|
||||
|
||||
## Attribution
|
||||
|
||||
Visual system, layouts, themes and the runtime keyboard model come from
|
||||
the upstream MIT-licensed [`lewislulu/html-ppt-skill`](https://github.com/lewislulu/html-ppt-skill). The
|
||||
LICENSE file ships at `skills/html-ppt/LICENSE`; please keep it in place when
|
||||
redistributing.
|
||||
489
skills/html-ppt-weekly-report/example.html
Normal file
489
skills/html-ppt-weekly-report/example.html
Normal file
|
|
@ -0,0 +1,489 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Growth Squad · Weekly W15</title>
|
||||
<style>/* html-ppt :: shared webfonts */
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@200;300;400;500;600;700;800;900&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@200;300;400;500;600;700;900&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@300;400;600;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,600;0,800;1,400&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@300;400;500;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Archivo+Black&display=swap');
|
||||
|
||||
</style>
|
||||
<style>/* html-ppt :: base.css — reset + shared tokens + layout primitives */
|
||||
/* Default tokens. Themes in assets/themes/*.css override the :root block. */
|
||||
:root {
|
||||
--bg: #ffffff;
|
||||
--bg-soft: #f7f7f8;
|
||||
--surface: #ffffff;
|
||||
--surface-2: #f2f2f4;
|
||||
--border: rgba(0,0,0,.08);
|
||||
--border-strong: rgba(0,0,0,.16);
|
||||
--text-1: #111216;
|
||||
--text-2: #55596a;
|
||||
--text-3: #8a8f9e;
|
||||
--accent: #3b6cff;
|
||||
--accent-2: #7a5cff;
|
||||
--accent-3: #ff5c8a;
|
||||
--good: #1aaf6c;
|
||||
--warn: #f5a524;
|
||||
--bad: #e0445a;
|
||||
--grad: linear-gradient(135deg,#3b6cff,#7a5cff 55%,#ff5c8a);
|
||||
--grad-soft: linear-gradient(135deg,#eef2ff,#f5ecff 55%,#ffeef5);
|
||||
--radius: 18px;
|
||||
--radius-sm: 12px;
|
||||
--radius-lg: 26px;
|
||||
--shadow: 0 10px 30px rgba(18,24,40,.08), 0 2px 6px rgba(18,24,40,.04);
|
||||
--shadow-lg: 0 24px 60px rgba(18,24,40,.14), 0 6px 16px rgba(18,24,40,.06);
|
||||
--font-sans: 'Inter','Noto Sans SC',-apple-system,BlinkMacSystemFont,Helvetica,Arial,sans-serif;
|
||||
--font-serif: 'Playfair Display','Noto Serif SC',Georgia,serif;
|
||||
--font-mono: 'JetBrains Mono','IBM Plex Mono',SFMono-Regular,Menlo,monospace;
|
||||
--font-display: var(--font-sans);
|
||||
--letter-tight: -.03em;
|
||||
--letter-normal: -.01em;
|
||||
--ease: cubic-bezier(.4,0,.2,1);
|
||||
}
|
||||
|
||||
*,*::before,*::after{box-sizing:border-box}
|
||||
html,body{margin:0;padding:0;background:var(--bg);color:var(--text-1);
|
||||
font-family:var(--font-sans);font-weight:400;line-height:1.6;
|
||||
-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;
|
||||
letter-spacing:var(--letter-normal)}
|
||||
img,svg,video{max-width:100%;display:block}
|
||||
a{color:var(--accent);text-decoration:none}
|
||||
a:hover{text-decoration:underline}
|
||||
code,kbd,pre,samp{font-family:var(--font-mono)}
|
||||
|
||||
/* ================= SLIDE SYSTEM ================= */
|
||||
.deck{position:relative;width:100vw;height:100vh;overflow:hidden;background:var(--bg)}
|
||||
.slide{
|
||||
position:absolute;inset:0;
|
||||
display:flex;flex-direction:column;justify-content:center;
|
||||
padding:72px 96px;
|
||||
box-sizing:border-box;
|
||||
opacity:0;pointer-events:none;
|
||||
transition:opacity .5s var(--ease), transform .5s var(--ease);
|
||||
transform:translateX(30px);
|
||||
overflow:hidden;
|
||||
}
|
||||
.slide.is-active{opacity:1;pointer-events:auto;transform:translateX(0);z-index:2}
|
||||
.slide.is-prev{transform:translateX(-30px)}
|
||||
|
||||
/* single-page standalone (used when a layout file is opened directly) */
|
||||
body.single .slide{position:relative;width:100vw;height:100vh;opacity:1;transform:none;pointer-events:auto}
|
||||
|
||||
/* ================= TYPOGRAPHY ================= */
|
||||
.eyebrow{font-size:13px;font-weight:500;letter-spacing:.16em;text-transform:uppercase;color:var(--text-3)}
|
||||
.kicker{font-size:14px;font-weight:600;color:var(--accent);letter-spacing:.08em;text-transform:uppercase}
|
||||
h1.title,.h1{font-family:var(--font-display);font-size:72px;line-height:1.05;font-weight:800;letter-spacing:var(--letter-tight);margin:0 0 18px;color:var(--text-1)}
|
||||
h2.title,.h2{font-family:var(--font-display);font-size:54px;line-height:1.1;font-weight:700;letter-spacing:var(--letter-tight);margin:0 0 14px}
|
||||
h3,.h3{font-size:32px;line-height:1.2;font-weight:600;letter-spacing:var(--letter-normal);margin:0 0 10px}
|
||||
h4,.h4{font-size:22px;line-height:1.3;font-weight:600;margin:0 0 8px}
|
||||
.lede{font-size:22px;line-height:1.55;color:var(--text-2);font-weight:300;max-width:62ch}
|
||||
.dim{color:var(--text-2)}
|
||||
.dim2{color:var(--text-3)}
|
||||
.mono{font-family:var(--font-mono)}
|
||||
.serif{font-family:var(--font-serif)}
|
||||
.gradient-text{background:var(--grad);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;color:transparent}
|
||||
|
||||
/* ================= LAYOUT PRIMITIVES ================= */
|
||||
.stack>*+*{margin-top:14px}
|
||||
.row{display:flex;gap:24px;align-items:center}
|
||||
.row.wrap{flex-wrap:wrap}
|
||||
.grid{display:grid;gap:24px}
|
||||
.g2{grid-template-columns:repeat(2,1fr)}
|
||||
.g3{grid-template-columns:repeat(3,1fr)}
|
||||
.g4{grid-template-columns:repeat(4,1fr)}
|
||||
.center{display:flex;align-items:center;justify-content:center;text-align:center}
|
||||
.fill{flex:1}
|
||||
.sp-t{padding-top:24px}.sp-b{padding-bottom:24px}
|
||||
.mt-s{margin-top:8px}.mt-m{margin-top:18px}.mt-l{margin-top:32px}
|
||||
.mb-s{margin-bottom:8px}.mb-m{margin-bottom:18px}.mb-l{margin-bottom:32px}
|
||||
|
||||
/* ================= CARDS ================= */
|
||||
.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);
|
||||
padding:26px 28px;box-shadow:var(--shadow);position:relative;overflow:hidden}
|
||||
.card-soft{background:var(--surface-2);border:1px solid var(--border)}
|
||||
.card-outline{background:transparent;border:1.5px solid var(--border-strong);box-shadow:none}
|
||||
.card-accent{background:var(--surface);border-top:3px solid var(--accent)}
|
||||
.card-hover{transition:transform .3s var(--ease),box-shadow .3s var(--ease)}
|
||||
.card-hover:hover{transform:translateY(-4px);box-shadow:var(--shadow-lg)}
|
||||
|
||||
.pill{display:inline-block;padding:4px 12px;border-radius:999px;font-size:12px;font-weight:500;
|
||||
background:var(--surface-2);color:var(--text-2);border:1px solid var(--border)}
|
||||
.pill-accent{background:color-mix(in srgb,var(--accent) 12%,transparent);color:var(--accent);border-color:color-mix(in srgb,var(--accent) 28%,transparent)}
|
||||
|
||||
/* ================= BARS / DIVIDERS ================= */
|
||||
.divider{height:1px;background:var(--border);width:100%}
|
||||
.divider-accent{height:3px;width:72px;background:var(--accent);border-radius:2px}
|
||||
|
||||
/* ================= CHROME (header/footer/progress) ================= */
|
||||
.deck-header{position:absolute;top:24px;left:40px;right:40px;display:flex;align-items:center;justify-content:space-between;
|
||||
font-size:12px;color:var(--text-3);letter-spacing:.12em;text-transform:uppercase;z-index:10;pointer-events:none}
|
||||
.deck-footer{position:absolute;bottom:24px;left:40px;right:40px;display:flex;align-items:center;justify-content:space-between;
|
||||
font-size:12px;color:var(--text-3);z-index:10;pointer-events:none}
|
||||
.slide-number::before{content:attr(data-current)}
|
||||
.slide-number::after{content:" / " attr(data-total)}
|
||||
.progress-bar{position:fixed;left:0;right:0;bottom:0;height:3px;background:transparent;z-index:20}
|
||||
.progress-bar > span{display:block;height:100%;width:0;background:var(--accent);transition:width .3s var(--ease)}
|
||||
|
||||
/* ================= PRESENTER / OVERVIEW ================= */
|
||||
.notes{display:none!important}
|
||||
.notes-overlay{position:fixed;inset:auto 0 0 0;max-height:42vh;background:rgba(20,22,30,.95);color:#e8ebf4;
|
||||
padding:20px 32px;font-size:16px;line-height:1.6;border-top:1px solid rgba(255,255,255,.1);transform:translateY(100%);
|
||||
transition:transform .3s var(--ease);z-index:40;overflow:auto;font-family:var(--font-sans)}
|
||||
.notes-overlay.open{transform:translateY(0)}
|
||||
.overview{position:fixed;inset:0;background:rgba(10,12,18,.92);backdrop-filter:blur(12px);z-index:50;
|
||||
display:none;padding:40px;overflow:auto}
|
||||
.overview.open{display:grid;grid-template-columns:repeat(4,1fr);gap:20px;align-content:start}
|
||||
.overview .thumb{background:var(--surface);border:1px solid var(--border);border-radius:12px;
|
||||
aspect-ratio:16/9;overflow:hidden;cursor:pointer;position:relative;color:var(--text-1);padding:16px;
|
||||
font-size:11px;transition:transform .2s var(--ease)}
|
||||
.overview .thumb:hover{transform:scale(1.04)}
|
||||
.overview .thumb .n{position:absolute;top:8px;left:10px;font-weight:700;font-size:14px;color:var(--text-3)}
|
||||
.overview .thumb .t{position:absolute;bottom:10px;left:14px;right:14px;font-weight:600;color:var(--text-1)}
|
||||
|
||||
/* ================= PRESENTER VIEW ================= */
|
||||
/* Presenter view opens in a separate popup window (S key).
|
||||
* All presenter styles are self-contained in the popup HTML generated by runtime.js.
|
||||
* The audience window (this file) is NOT affected — it stays as normal deck view.
|
||||
* Only the .notes class below is needed to hide speaker notes from audience. */
|
||||
|
||||
/* ================= UTILITY ================= */
|
||||
.hidden{display:none!important}
|
||||
.nowrap{white-space:nowrap}
|
||||
.tr{text-align:right}.tc{text-align:center}.tl{text-align:left}
|
||||
.uppercase{text-transform:uppercase;letter-spacing:.12em}
|
||||
|
||||
/* ================= PRINT ================= */
|
||||
@media print{
|
||||
.slide{position:relative;opacity:1!important;transform:none!important;page-break-after:always;height:100vh}
|
||||
.deck-header,.deck-footer,.progress-bar,.notes-overlay,.overview{display:none!important}
|
||||
}
|
||||
|
||||
</style>
|
||||
<style>/* html-ppt :: animations.css
|
||||
* Apply by adding class="anim-<name>" or data-anim="<name>".
|
||||
* Durations are deliberately snappy; tweak --anim-dur per element.
|
||||
*/
|
||||
:root{--anim-dur:.7s;--anim-ease:cubic-bezier(.4,0,.2,1)}
|
||||
|
||||
/* ---------- FADE DIRECTIONALS ---------- */
|
||||
@keyframes kf-fade-up{from{opacity:0;transform:translateY(32px)}to{opacity:1;transform:none}}
|
||||
@keyframes kf-fade-down{from{opacity:0;transform:translateY(-32px)}to{opacity:1;transform:none}}
|
||||
@keyframes kf-fade-left{from{opacity:0;transform:translateX(-40px)}to{opacity:1;transform:none}}
|
||||
@keyframes kf-fade-right{from{opacity:0;transform:translateX(40px)}to{opacity:1;transform:none}}
|
||||
.anim-fade-up{animation:kf-fade-up var(--anim-dur) var(--anim-ease) both}
|
||||
.anim-fade-down{animation:kf-fade-down var(--anim-dur) var(--anim-ease) both}
|
||||
.anim-fade-left{animation:kf-fade-left var(--anim-dur) var(--anim-ease) both}
|
||||
.anim-fade-right{animation:kf-fade-right var(--anim-dur) var(--anim-ease) both}
|
||||
|
||||
/* ---------- RISE / DROP / ZOOM / BLUR / GLITCH ---------- */
|
||||
@keyframes kf-rise{from{opacity:0;transform:translateY(60px) scale(.97);filter:blur(6px)}to{opacity:1;transform:none;filter:none}}
|
||||
@keyframes kf-drop{from{opacity:0;transform:translateY(-60px) scale(.97)}to{opacity:1;transform:none}}
|
||||
@keyframes kf-zoom{0%{opacity:0;transform:scale(.6)}60%{transform:scale(1.04)}100%{opacity:1;transform:scale(1)}}
|
||||
@keyframes kf-blur{from{opacity:0;filter:blur(18px)}to{opacity:1;filter:none}}
|
||||
@keyframes kf-glitch{0%{opacity:0;transform:translateX(0);clip-path:inset(0 0 0 0)}
|
||||
20%{opacity:1;transform:translateX(-6px);clip-path:inset(20% 0 30% 0)}
|
||||
40%{transform:translateX(4px);clip-path:inset(50% 0 10% 0)}
|
||||
60%{transform:translateX(-3px);clip-path:inset(10% 0 60% 0)}
|
||||
80%{transform:translateX(2px);clip-path:inset(0 0 0 0)}
|
||||
100%{opacity:1;transform:none}}
|
||||
.anim-rise-in{animation:kf-rise .9s var(--anim-ease) both}
|
||||
.anim-drop-in{animation:kf-drop .8s var(--anim-ease) both}
|
||||
.anim-zoom-pop{animation:kf-zoom .7s cubic-bezier(.22,1.3,.36,1) both}
|
||||
.anim-blur-in{animation:kf-blur .8s var(--anim-ease) both}
|
||||
.anim-glitch-in{animation:kf-glitch .8s steps(5,end) both}
|
||||
|
||||
/* ---------- TYPEWRITER ---------- */
|
||||
.anim-typewriter{display:inline-block;overflow:hidden;white-space:nowrap;border-right:2px solid currentColor;
|
||||
width:0;animation:kf-type 2.4s steps(40,end) forwards, kf-caret 1s step-end infinite}
|
||||
@keyframes kf-type{to{width:100%}}
|
||||
@keyframes kf-caret{50%{border-color:transparent}}
|
||||
|
||||
/* ---------- GLOW / SHIMMER / GRADIENT-FLOW ---------- */
|
||||
@keyframes kf-neon{0%,100%{text-shadow:0 0 8px var(--accent),0 0 20px var(--accent)}
|
||||
50%{text-shadow:0 0 16px var(--accent),0 0 40px var(--accent),0 0 80px var(--accent)}}
|
||||
.anim-neon-glow{animation:kf-neon 2s ease-in-out infinite}
|
||||
|
||||
.anim-shimmer-sweep{position:relative;overflow:hidden}
|
||||
.anim-shimmer-sweep::after{content:"";position:absolute;inset:0;
|
||||
background:linear-gradient(110deg,transparent 40%,rgba(255,255,255,.55) 50%,transparent 60%);
|
||||
transform:translateX(-100%);animation:kf-shimmer 2.4s var(--anim-ease) infinite}
|
||||
@keyframes kf-shimmer{to{transform:translateX(100%)}}
|
||||
|
||||
.anim-gradient-flow{background:linear-gradient(90deg,var(--accent),var(--accent-2,var(--accent)),var(--accent-3,var(--accent)),var(--accent));
|
||||
background-size:300% 100%;-webkit-background-clip:text;background-clip:text;color:transparent;-webkit-text-fill-color:transparent;
|
||||
animation:kf-gradflow 4s linear infinite}
|
||||
@keyframes kf-gradflow{to{background-position:300% 0}}
|
||||
|
||||
/* ---------- STAGGER LIST ---------- */
|
||||
.anim-stagger-list > *{opacity:0;animation:kf-rise .65s var(--anim-ease) both}
|
||||
.anim-stagger-list > *:nth-child(1){animation-delay:.05s}
|
||||
.anim-stagger-list > *:nth-child(2){animation-delay:.15s}
|
||||
.anim-stagger-list > *:nth-child(3){animation-delay:.25s}
|
||||
.anim-stagger-list > *:nth-child(4){animation-delay:.35s}
|
||||
.anim-stagger-list > *:nth-child(5){animation-delay:.45s}
|
||||
.anim-stagger-list > *:nth-child(6){animation-delay:.55s}
|
||||
.anim-stagger-list > *:nth-child(7){animation-delay:.65s}
|
||||
.anim-stagger-list > *:nth-child(8){animation-delay:.75s}
|
||||
.anim-stagger-list > *:nth-child(n+9){animation-delay:.85s}
|
||||
|
||||
/* ---------- COUNTER-UP (JS-driven, marker class only) ---------- */
|
||||
.counter{font-variant-numeric:tabular-nums}
|
||||
|
||||
/* ---------- SVG PATH DRAW ---------- */
|
||||
.anim-path-draw path,.anim-path-draw line,.anim-path-draw polyline,.anim-path-draw circle,.anim-path-draw rect{
|
||||
stroke-dasharray:1000;stroke-dashoffset:1000;animation:kf-draw 2s var(--anim-ease) forwards}
|
||||
@keyframes kf-draw{to{stroke-dashoffset:0}}
|
||||
|
||||
/* ---------- PARALLAX TILT (hover) ---------- */
|
||||
.anim-parallax-tilt{transform-style:preserve-3d;transition:transform .4s var(--anim-ease)}
|
||||
.anim-parallax-tilt:hover{transform:perspective(900px) rotateX(6deg) rotateY(-8deg) translateZ(10px)}
|
||||
|
||||
/* ---------- CARD FLIP 3D ---------- */
|
||||
@keyframes kf-flip{from{transform:perspective(1200px) rotateY(-90deg);opacity:0}
|
||||
to{transform:perspective(1200px) rotateY(0);opacity:1}}
|
||||
.anim-card-flip-3d{animation:kf-flip .9s var(--anim-ease) both;transform-style:preserve-3d;backface-visibility:hidden}
|
||||
|
||||
/* ---------- CUBE ROTATE 3D ---------- */
|
||||
@keyframes kf-cube{from{transform:perspective(1200px) rotateX(20deg) rotateY(-90deg) translateZ(-200px);opacity:0}
|
||||
to{transform:perspective(1200px) rotateX(0) rotateY(0) translateZ(0);opacity:1}}
|
||||
.anim-cube-rotate-3d{animation:kf-cube 1s var(--anim-ease) both}
|
||||
|
||||
/* ---------- PAGE TURN 3D ---------- */
|
||||
@keyframes kf-pageturn{from{transform:perspective(1600px) rotateY(-85deg);transform-origin:left center;opacity:0}
|
||||
to{transform:perspective(1600px) rotateY(0);opacity:1}}
|
||||
.anim-page-turn-3d{animation:kf-pageturn 1s var(--anim-ease) both;transform-origin:left center}
|
||||
|
||||
/* ---------- PERSPECTIVE ZOOM ---------- */
|
||||
@keyframes kf-pzoom{from{opacity:0;transform:perspective(1400px) translateZ(-400px) rotateX(12deg)}
|
||||
to{opacity:1;transform:none}}
|
||||
.anim-perspective-zoom{animation:kf-pzoom 1s var(--anim-ease) both}
|
||||
|
||||
/* ---------- MARQUEE SCROLL ---------- */
|
||||
.anim-marquee-scroll{display:flex;gap:48px;white-space:nowrap;animation:kf-marquee 20s linear infinite}
|
||||
@keyframes kf-marquee{from{transform:translateX(0)}to{transform:translateX(-50%)}}
|
||||
|
||||
/* ---------- KEN BURNS ---------- */
|
||||
@keyframes kf-kenburns{0%{transform:scale(1) translate(0,0)}100%{transform:scale(1.15) translate(-2%,-1%)}}
|
||||
.anim-kenburns{animation:kf-kenburns 14s ease-in-out infinite alternate}
|
||||
|
||||
/* ---------- CONFETTI BURST (pseudo — pure CSS sparkles) ---------- */
|
||||
.anim-confetti-burst{position:relative}
|
||||
.anim-confetti-burst::before,.anim-confetti-burst::after{
|
||||
content:"";position:absolute;top:50%;left:50%;width:8px;height:8px;border-radius:50%;
|
||||
background:var(--accent);box-shadow:
|
||||
20px -30px 0 var(--accent-2,var(--accent)),-25px -20px 0 var(--accent-3,var(--accent)),
|
||||
30px 20px 0 var(--good,#1aaf6c),-30px 25px 0 var(--warn,#f5a524),
|
||||
40px -10px 0 var(--bad,#e0445a),-45px 0 0 var(--accent),
|
||||
10px 40px 0 var(--accent-2,var(--accent)),-15px -40px 0 var(--accent-3,var(--accent));
|
||||
opacity:0;animation:kf-confetti 1.2s var(--anim-ease) forwards}
|
||||
.anim-confetti-burst::after{animation-delay:.15s;transform:rotate(45deg)}
|
||||
@keyframes kf-confetti{0%{opacity:0;transform:scale(.2)}30%{opacity:1}100%{opacity:0;transform:scale(2.2)}}
|
||||
|
||||
/* ---------- SPOTLIGHT ---------- */
|
||||
@keyframes kf-spot{0%{clip-path:circle(0% at 50% 50%)}100%{clip-path:circle(140% at 50% 50%)}}
|
||||
.anim-spotlight{animation:kf-spot 1.1s var(--anim-ease) both}
|
||||
|
||||
/* ---------- MORPH SHAPE (SVG) ---------- */
|
||||
.anim-morph-shape path{animation:kf-morph 6s ease-in-out infinite alternate}
|
||||
@keyframes kf-morph{0%{d:path("M60,120 Q120,20 180,120 T300,120")}
|
||||
100%{d:path("M60,120 Q120,220 180,120 T300,120")}}
|
||||
|
||||
/* ---------- RIPPLE REVEAL ---------- */
|
||||
@keyframes kf-ripple{0%{clip-path:circle(0% at 20% 80%);opacity:.4}
|
||||
100%{clip-path:circle(160% at 20% 80%);opacity:1}}
|
||||
.anim-ripple-reveal{animation:kf-ripple 1.2s var(--anim-ease) both}
|
||||
|
||||
/* reduced motion */
|
||||
@media (prefers-reduced-motion: reduce){
|
||||
[class*="anim-"]{animation:none!important;transition:none!important}
|
||||
}
|
||||
|
||||
</style>
|
||||
<style>/* weekly-report — corporate clarity */
|
||||
.tpl-weekly-report{
|
||||
--bg:#fafbfc;--bg-soft:#f3f5f9;--surface:#ffffff;--surface-2:#f3f5f9;
|
||||
--border:rgba(22,30,55,.09);--border-strong:rgba(22,30,55,.2);
|
||||
--text-1:#161e37;--text-2:#50586b;--text-3:#8b92a5;
|
||||
--accent:#2e63eb;--accent-2:#0ea5b5;--accent-3:#f59e0b;
|
||||
--good:#10b981;--warn:#f59e0b;--bad:#ef4444;
|
||||
--grad:linear-gradient(120deg,#2e63eb,#0ea5b5);
|
||||
--radius:14px;--radius-lg:18px;
|
||||
--shadow:0 6px 20px rgba(22,30,55,.06),0 1px 3px rgba(22,30,55,.04);
|
||||
font-family:'Inter','Noto Sans SC',sans-serif;
|
||||
}
|
||||
.tpl-weekly-report .slide{padding:64px 88px;background:var(--bg)}
|
||||
.tpl-weekly-report .h1{font-size:64px;line-height:1.05;font-weight:800;letter-spacing:-.025em}
|
||||
.tpl-weekly-report .h2{font-size:42px;font-weight:700;letter-spacing:-.02em}
|
||||
.tpl-weekly-report .kicker{color:var(--accent);font-size:12px;font-weight:700}
|
||||
.tpl-weekly-report .cover-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:48px}
|
||||
.tpl-weekly-report .logo{font-weight:800;font-size:18px;letter-spacing:-.01em}
|
||||
.tpl-weekly-report .logo::before{content:"■";color:var(--accent);margin-right:8px}
|
||||
.tpl-weekly-report .week-chip{display:inline-block;padding:8px 18px;border-radius:8px;background:var(--surface);border:1px solid var(--border);font-family:'JetBrains Mono',monospace;font-size:13px;color:var(--text-2)}
|
||||
.tpl-weekly-report .kpi{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:24px 26px;position:relative;overflow:hidden}
|
||||
.tpl-weekly-report .kpi .label{font-size:12px;text-transform:uppercase;letter-spacing:.08em;color:var(--text-3);font-weight:600}
|
||||
.tpl-weekly-report .kpi .value{font-size:48px;font-weight:800;letter-spacing:-.03em;margin-top:8px;line-height:1}
|
||||
.tpl-weekly-report .kpi .delta{display:inline-flex;align-items:center;gap:4px;padding:3px 8px;border-radius:6px;font-size:12px;font-weight:700;margin-top:10px}
|
||||
.tpl-weekly-report .kpi .delta.up{background:rgba(16,185,129,.12);color:var(--good)}
|
||||
.tpl-weekly-report .kpi .delta.down{background:rgba(239,68,68,.12);color:var(--bad)}
|
||||
.tpl-weekly-report .kpi .delta.flat{background:rgba(139,146,165,.14);color:var(--text-2)}
|
||||
.tpl-weekly-report .kpi::before{content:"";position:absolute;left:0;top:0;bottom:0;width:3px;background:var(--accent)}
|
||||
.tpl-weekly-report .kpi.good::before{background:var(--good)}
|
||||
.tpl-weekly-report .kpi.warn::before{background:var(--warn)}
|
||||
.tpl-weekly-report .kpi.bad::before{background:var(--bad)}
|
||||
.tpl-weekly-report .ship-item{display:flex;gap:14px;padding:14px 0;border-bottom:1px solid var(--border)}
|
||||
.tpl-weekly-report .ship-item .tag{flex:none;padding:3px 10px;border-radius:6px;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;height:22px;display:inline-flex;align-items:center}
|
||||
.tpl-weekly-report .tag.feat{background:rgba(46,99,235,.12);color:var(--accent)}
|
||||
.tpl-weekly-report .tag.fix{background:rgba(16,185,129,.12);color:var(--good)}
|
||||
.tpl-weekly-report .tag.exp{background:rgba(245,158,11,.14);color:var(--warn)}
|
||||
.tpl-weekly-report .tag.infra{background:rgba(14,165,181,.12);color:var(--accent-2)}
|
||||
.tpl-weekly-report .ship-item b{color:var(--text-1);font-weight:600}
|
||||
.tpl-weekly-report .ship-item span.owner{margin-left:auto;color:var(--text-3);font-size:12px;font-family:'JetBrains Mono',monospace}
|
||||
.tpl-weekly-report .chart{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:28px}
|
||||
.tpl-weekly-report .chart-bars{display:flex;align-items:flex-end;gap:16px;height:220px;margin-top:20px}
|
||||
.tpl-weekly-report .chart-bars .col{flex:1;display:flex;flex-direction:column;align-items:center;gap:6px;position:relative}
|
||||
.tpl-weekly-report .chart-bars .col .b{width:100%;background:var(--grad);border-radius:6px 6px 0 0;min-height:6px;position:relative}
|
||||
.tpl-weekly-report .chart-bars .col .b::after{content:attr(data-v);position:absolute;top:-22px;left:0;right:0;text-align:center;font-size:12px;font-weight:700;color:var(--text-1)}
|
||||
.tpl-weekly-report .chart-bars .col .lbl{font-size:11px;color:var(--text-3);font-family:'JetBrains Mono',monospace}
|
||||
.tpl-weekly-report .blocker{background:var(--surface);border-left:3px solid var(--bad);padding:16px 20px;border-radius:8px;margin-bottom:12px}
|
||||
.tpl-weekly-report .blocker h4{font-size:16px;margin-bottom:4px}
|
||||
.tpl-weekly-report .blocker p{font-size:13px;color:var(--text-2);margin:0}
|
||||
.tpl-weekly-report .blocker .meta{font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--text-3);margin-top:6px}
|
||||
.tpl-weekly-report .next-row{display:grid;grid-template-columns:110px 1fr;gap:16px;padding:14px 0;border-bottom:1px dashed var(--border);align-items:baseline}
|
||||
.tpl-weekly-report .next-row .owner{font-family:'JetBrains Mono',monospace;font-size:12px;color:var(--accent)}
|
||||
.tpl-weekly-report .next-row .task{color:var(--text-1);font-weight:500}
|
||||
.tpl-weekly-report .next-row .task span{color:var(--text-3);font-size:12px;margin-left:8px}
|
||||
.tpl-weekly-report .lede{color:var(--text-2)}
|
||||
.tpl-weekly-report .card{background:var(--surface)}
|
||||
|
||||
</style>
|
||||
<style>
|
||||
/* Static-preview fallback (runtime.js is absent — keep every slide visible) */
|
||||
.deck{height:auto;min-height:100vh;overflow:visible}
|
||||
.slide{position:relative;inset:auto;opacity:1;pointer-events:auto;transform:none;height:100vh;page-break-after:always}
|
||||
.deck-header,.deck-footer,.slide-number,.progress-bar,.notes-overlay,.overview{pointer-events:none}
|
||||
.notes{display:none!important}
|
||||
</style></head>
|
||||
<body class="tpl-weekly-report">
|
||||
<div class="deck">
|
||||
|
||||
<!-- 1. Cover -->
|
||||
<section class="slide" data-title="Cover">
|
||||
<div class="cover-head">
|
||||
<div class="logo">Growth Squad</div>
|
||||
<div class="week-chip">W15 · 2026-04-07 → 2026-04-13</div>
|
||||
</div>
|
||||
<p class="kicker">WEEKLY REPORT</p>
|
||||
<h1 class="h1 mt-s">本周:付费转化率<br>回到了 <span style="color:var(--accent)">3.8%</span>。</h1>
|
||||
<p class="lede mt-m">6 个发布,3 个实验收敛,1 个阻塞项升级。整体健康。</p>
|
||||
<div class="deck-footer"><span>Prepared by @lewis · reviewed by @may</span><span class="slide-number" data-current="1" data-total="7"></span></div>
|
||||
</section>
|
||||
|
||||
<!-- 2. KPI -->
|
||||
<section class="slide" data-title="KPIs">
|
||||
<p class="kicker">HIGHLIGHTS · KPIs</p>
|
||||
<h2 class="h2">本周核心指标</h2>
|
||||
<div class="grid g4 mt-l">
|
||||
<div class="kpi good"><div class="label">Paid conv.</div><div class="value">3.82%</div><div class="delta up">▲ +0.4 pts WoW</div></div>
|
||||
<div class="kpi good"><div class="label">MRR</div><div class="value">$148k</div><div class="delta up">▲ +6.1%</div></div>
|
||||
<div class="kpi"><div class="label">Signups</div><div class="value">12,430</div><div class="delta flat">— +0.3%</div></div>
|
||||
<div class="kpi bad"><div class="label">D7 retention</div><div class="value">41%</div><div class="delta down">▼ -1.8 pts</div></div>
|
||||
<div class="kpi good"><div class="label">NPS</div><div class="value">64</div><div class="delta up">▲ +3</div></div>
|
||||
<div class="kpi"><div class="label">Support tickets</div><div class="value">318</div><div class="delta flat">— -12</div></div>
|
||||
<div class="kpi warn"><div class="label">p95 latency</div><div class="value">412ms</div><div class="delta down">▼ +38ms</div></div>
|
||||
<div class="kpi good"><div class="label">Deploys</div><div class="value">37</div><div class="delta up">▲ +9</div></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 3. Shipped -->
|
||||
<section class="slide" data-title="Shipped">
|
||||
<p class="kicker">SHIPPED THIS WEEK · 6 items</p>
|
||||
<h2 class="h2">Shipped</h2>
|
||||
<div class="mt-l" style="max-width:980px">
|
||||
<div class="ship-item"><span class="tag feat">FEAT</span><div><b>New onboarding checklist v3</b><p class="dim" style="font-size:13px;margin:2px 0 0">4-step checklist replaces the old 7-step modal. A/B won +18% activation.</p></div><span class="owner">@may</span></div>
|
||||
<div class="ship-item"><span class="tag feat">FEAT</span><div><b>Stripe Tax auto-filing</b><p class="dim" style="font-size:13px;margin:2px 0 0">Quarterly filings now handled for 12 US states via Stripe Tax API.</p></div><span class="owner">@raj</span></div>
|
||||
<div class="ship-item"><span class="tag exp">EXP</span><div><b>Pricing page hero test</b><p class="dim" style="font-size:13px;margin:2px 0 0">"From $29" vs "Free trial" headline. Free-trial wins +22% click-through.</p></div><span class="owner">@lewis</span></div>
|
||||
<div class="ship-item"><span class="tag fix">FIX</span><div><b>Edge case in SSO redirect</b><p class="dim" style="font-size:13px;margin:2px 0 0">Google Workspace users with custom domains now land on the correct workspace.</p></div><span class="owner">@eli</span></div>
|
||||
<div class="ship-item"><span class="tag infra">INFRA</span><div><b>Postgres 16 upgrade</b><p class="dim" style="font-size:13px;margin:2px 0 0">Zero-downtime migration. Query p50 down 14%, p95 down 9%.</p></div><span class="owner">@raj</span></div>
|
||||
<div class="ship-item"><span class="tag feat">FEAT</span><div><b>Referral rewards v1</b><p class="dim" style="font-size:13px;margin:2px 0 0">Both sides get 1 month free. Dashboard + email flow live behind flag.</p></div><span class="owner">@may</span></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 4. Metrics chart -->
|
||||
<section class="slide" data-title="Metrics">
|
||||
<p class="kicker">METRIC DEEP-DIVE</p>
|
||||
<h2 class="h2">Paid conversion, last 8 weeks</h2>
|
||||
<div class="chart mt-l">
|
||||
<div class="row" style="justify-content:space-between"><h4>Paid conv. rate · weekly</h4><span class="pill" style="background:var(--surface-2);color:var(--text-2)">target: 4.0%</span></div>
|
||||
<div class="chart-bars">
|
||||
<div class="col"><div class="b" data-v="3.1%" style="height:58%"></div><div class="lbl">W08</div></div>
|
||||
<div class="col"><div class="b" data-v="3.3%" style="height:64%"></div><div class="lbl">W09</div></div>
|
||||
<div class="col"><div class="b" data-v="3.5%" style="height:72%"></div><div class="lbl">W10</div></div>
|
||||
<div class="col"><div class="b" data-v="3.6%" style="height:75%"></div><div class="lbl">W11</div></div>
|
||||
<div class="col"><div class="b" data-v="3.4%" style="height:68%"></div><div class="lbl">W12</div></div>
|
||||
<div class="col"><div class="b" data-v="3.0%" style="height:55%"></div><div class="lbl">W13</div></div>
|
||||
<div class="col"><div class="b" data-v="3.4%" style="height:68%"></div><div class="lbl">W14</div></div>
|
||||
<div class="col"><div class="b" data-v="3.8%" style="height:88%"></div><div class="lbl">W15</div></div>
|
||||
</div>
|
||||
<p class="dim mt-m" style="font-size:13px;margin-top:36px">Drop in W13 tracked to a broken Stripe webhook (fixed W14). Rebound in W15 is driven by the new onboarding checklist.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 5. Blockers -->
|
||||
<section class="slide" data-title="Blockers">
|
||||
<p class="kicker">BLOCKERS · 3 items</p>
|
||||
<h2 class="h2">Needs attention</h2>
|
||||
<div class="mt-l" style="max-width:900px">
|
||||
<div class="blocker">
|
||||
<h4>p95 latency regressed to 412ms (+38ms)</h4>
|
||||
<p>Traced to the new recommender service under load. Adding caching layer + connection pooling.</p>
|
||||
<div class="meta">owner: @raj · ETA: W16 Wed · severity: medium</div>
|
||||
</div>
|
||||
<div class="blocker">
|
||||
<h4>Apple Pay disabled in EU for 3 days</h4>
|
||||
<p>Stripe credential rotation wasn't synced to the EU account. Fixed, but cost ~$4.2k in lost checkouts.</p>
|
||||
<div class="meta">owner: @eli · severity: high · postmortem in progress</div>
|
||||
</div>
|
||||
<div class="blocker">
|
||||
<h4>D7 retention down 1.8 points</h4>
|
||||
<p>Cohort analysis shows it's isolated to the free-trial pricing test. Need to decide: kill test, or push through W16.</p>
|
||||
<div class="meta">owner: @lewis · needs decision from @may by Monday</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 6. Next week -->
|
||||
<section class="slide" data-title="Next Week">
|
||||
<p class="kicker">NEXT WEEK · W16 plan</p>
|
||||
<h2 class="h2">下周重点</h2>
|
||||
<div class="mt-l" style="max-width:960px">
|
||||
<div class="next-row"><div class="owner">@raj</div><div class="task"><b>Ship recommender cache layer</b><span>blocker · must land Wed</span></div></div>
|
||||
<div class="next-row"><div class="owner">@may</div><div class="task"><b>Referral rewards · flag rollout to 100%</b><span>milestone · targets +3% WoW signups</span></div></div>
|
||||
<div class="next-row"><div class="owner">@lewis</div><div class="task"><b>Pricing test: decision doc + readout</b><span>deadline Mon noon</span></div></div>
|
||||
<div class="next-row"><div class="owner">@eli</div><div class="task"><b>Apple Pay postmortem + runbook update</b><span>include in W16 eng review</span></div></div>
|
||||
<div class="next-row"><div class="owner">squad</div><div class="task"><b>Q2 OKR planning offsite</b><span>Thu 2–5pm · async pre-reads Wed</span></div></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 7. Thanks -->
|
||||
<section class="slide center tc" data-title="Thanks">
|
||||
<div>
|
||||
<p class="kicker">FIN · week 15</p>
|
||||
<h1 class="h1" style="font-size:100px">Thanks, team 🫶</h1>
|
||||
<p class="lede" style="margin:16px auto">Solid week. Rebound earned, not luck.</p>
|
||||
<div class="row mt-l" style="justify-content:center;gap:16px">
|
||||
<span class="week-chip">Next report: Mon W16</span>
|
||||
<span class="week-chip">questions → #growth-squad</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
</body></html>
|
||||
79
skills/html-ppt-xhs-pastel-card/SKILL.md
Normal file
79
skills/html-ppt-xhs-pastel-card/SKILL.md
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
---
|
||||
name: html-ppt-xhs-pastel-card
|
||||
description: 柔和马卡龙慢生活 deck — 奶油 #fef8f1 底 + 三个柔光 blob、Playfair 斜体衬线 display 标题混 sans 正文、28px 圆角马卡龙卡片(桃 / 薄荷 / 天 / 紫 / 柠 / 玫)、Playfair 斜体 01-04 序号、SVG donut 图、chip+page 顶栏。适合生活方式 / 个人成长 / 慢生活 / 情绪类内容,"杂志、手作、不太科技"的感觉。
|
||||
triggers:
|
||||
- "pastel"
|
||||
- "macaron"
|
||||
- "lifestyle"
|
||||
- "slow living"
|
||||
- "慢生活"
|
||||
- "生活方式"
|
||||
- "个人成长"
|
||||
od:
|
||||
mode: deck
|
||||
scenario: personal
|
||||
featured: 33
|
||||
upstream: "https://github.com/lewislulu/html-ppt-skill"
|
||||
preview:
|
||||
type: html
|
||||
entry: index.html
|
||||
design_system:
|
||||
requires: false
|
||||
speaker_notes: true
|
||||
animations: true
|
||||
example_prompt: "用 html-ppt-xhs-pastel-card 模板做一份慢生活主题图文。奶油底 + 马卡龙圆角卡片 + Playfair 斜体序号 + donut 图。先告诉我主题(休息 / 暂停 / 自我照顾…)和 5-7 个想说的点。"
|
||||
---
|
||||
# HTML PPT · 柔和马卡龙慢生活
|
||||
|
||||
A focused entry point into the [`html-ppt`](../html-ppt/SKILL.md) master skill that lands the user directly on the **`xhs-pastel-card`** full-deck template.
|
||||
|
||||
## When this card is picked
|
||||
|
||||
The Examples gallery wires "Use this prompt" to the example_prompt above. When you accept that prompt, this card is the right pick if the user wants exactly the visual identity of `xhs-pastel-card` (see the upstream [full-decks catalog](../html-ppt/references/full-decks.md) for screenshots and rationale).
|
||||
|
||||
## How to author the deck
|
||||
|
||||
1. **Read the master skill first.** All authoring rules live in
|
||||
[`skills/html-ppt/SKILL.md`](../html-ppt/SKILL.md) — content/audience checklist,
|
||||
token rules, layout reuse, presenter mode, the keyboard runtime, and the
|
||||
"never put presenter-only text on the slide" rule.
|
||||
2. **Start from the matching template folder:**
|
||||
`skills/html-ppt/templates/full-decks/xhs-pastel-card/` — copy `index.html` and
|
||||
`style.css` into the project, keep the `.tpl-xhs-pastel-card` body class.
|
||||
3. **Bring the shared runtime with the template.** The upstream
|
||||
`index.html` links the shared CSS/JS via `../../../assets/...` because it
|
||||
sits three folders deep inside `skills/html-ppt/templates/full-decks/`.
|
||||
Once you copy `index.html` into the project, those parent-relative URLs
|
||||
no longer resolve and `base.css`, `animations.css`, and `runtime.js`
|
||||
will 404 — meaning the deck never activates and slide navigation is
|
||||
dead. Pick one of these two recipes per project:
|
||||
- **Recipe A — copy + rewrite (preferred):** copy
|
||||
`skills/html-ppt/assets/fonts.css`, `skills/html-ppt/assets/base.css`,
|
||||
`skills/html-ppt/assets/animations/animations.css`, and
|
||||
`skills/html-ppt/assets/runtime.js` into a project-local
|
||||
`assets/` (with `assets/animations/animations.css`), then rewrite the
|
||||
four `<link>`/`<script>` tags in `index.html` from
|
||||
`../../../assets/...` to the matching project-local paths
|
||||
(`assets/fonts.css`, `assets/base.css`,
|
||||
`assets/animations/animations.css`, `assets/runtime.js`).
|
||||
- **Recipe B — inline:** read the same four files and replace each
|
||||
`<link rel="stylesheet" href="../../../assets/...">` with a
|
||||
`<style>...</style>` containing the file's contents, and the
|
||||
`<script src="../../../assets/runtime.js">` with a
|
||||
`<script>...</script>` containing `runtime.js`. Yields a single
|
||||
self-contained `index.html`.
|
||||
Either way, do not ship the upstream `../../../assets/...` URLs
|
||||
verbatim into a project artifact — they only work in-tree.
|
||||
4. **Pick a theme.** Default tokens look fine; if the user wants a different
|
||||
feel, swap in any of the 36 themes from `skills/html-ppt/assets/themes/*.css`
|
||||
via `<link id="theme-link">` and let `T` cycle.
|
||||
5. **Replace demo content, not classes.** The `.tpl-xhs-pastel-card` scoped CSS only
|
||||
recognises the structural classes shipped in the template — keep them.
|
||||
6. **Speaker notes go inside `<aside class="notes">` or `<div class="notes">`** — never as visible text on the slide.
|
||||
|
||||
## Attribution
|
||||
|
||||
Visual system, layouts, themes and the runtime keyboard model come from
|
||||
the upstream MIT-licensed [`lewislulu/html-ppt-skill`](https://github.com/lewislulu/html-ppt-skill). The
|
||||
LICENSE file ships at `skills/html-ppt/LICENSE`; please keep it in place when
|
||||
redistributing.
|
||||
381
skills/html-ppt-xhs-pastel-card/example.html
Normal file
381
skills/html-ppt-xhs-pastel-card/example.html
Normal file
|
|
@ -0,0 +1,381 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>XHS Pastel Card</title>
|
||||
<style>/* html-ppt :: shared webfonts */
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@200;300;400;500;600;700;800;900&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@200;300;400;500;600;700;900&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@300;400;600;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,600;0,800;1,400&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@300;400;500;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Archivo+Black&display=swap');
|
||||
|
||||
</style>
|
||||
<style>/* html-ppt :: base.css — reset + shared tokens + layout primitives */
|
||||
/* Default tokens. Themes in assets/themes/*.css override the :root block. */
|
||||
:root {
|
||||
--bg: #ffffff;
|
||||
--bg-soft: #f7f7f8;
|
||||
--surface: #ffffff;
|
||||
--surface-2: #f2f2f4;
|
||||
--border: rgba(0,0,0,.08);
|
||||
--border-strong: rgba(0,0,0,.16);
|
||||
--text-1: #111216;
|
||||
--text-2: #55596a;
|
||||
--text-3: #8a8f9e;
|
||||
--accent: #3b6cff;
|
||||
--accent-2: #7a5cff;
|
||||
--accent-3: #ff5c8a;
|
||||
--good: #1aaf6c;
|
||||
--warn: #f5a524;
|
||||
--bad: #e0445a;
|
||||
--grad: linear-gradient(135deg,#3b6cff,#7a5cff 55%,#ff5c8a);
|
||||
--grad-soft: linear-gradient(135deg,#eef2ff,#f5ecff 55%,#ffeef5);
|
||||
--radius: 18px;
|
||||
--radius-sm: 12px;
|
||||
--radius-lg: 26px;
|
||||
--shadow: 0 10px 30px rgba(18,24,40,.08), 0 2px 6px rgba(18,24,40,.04);
|
||||
--shadow-lg: 0 24px 60px rgba(18,24,40,.14), 0 6px 16px rgba(18,24,40,.06);
|
||||
--font-sans: 'Inter','Noto Sans SC',-apple-system,BlinkMacSystemFont,Helvetica,Arial,sans-serif;
|
||||
--font-serif: 'Playfair Display','Noto Serif SC',Georgia,serif;
|
||||
--font-mono: 'JetBrains Mono','IBM Plex Mono',SFMono-Regular,Menlo,monospace;
|
||||
--font-display: var(--font-sans);
|
||||
--letter-tight: -.03em;
|
||||
--letter-normal: -.01em;
|
||||
--ease: cubic-bezier(.4,0,.2,1);
|
||||
}
|
||||
|
||||
*,*::before,*::after{box-sizing:border-box}
|
||||
html,body{margin:0;padding:0;background:var(--bg);color:var(--text-1);
|
||||
font-family:var(--font-sans);font-weight:400;line-height:1.6;
|
||||
-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;
|
||||
letter-spacing:var(--letter-normal)}
|
||||
img,svg,video{max-width:100%;display:block}
|
||||
a{color:var(--accent);text-decoration:none}
|
||||
a:hover{text-decoration:underline}
|
||||
code,kbd,pre,samp{font-family:var(--font-mono)}
|
||||
|
||||
/* ================= SLIDE SYSTEM ================= */
|
||||
.deck{position:relative;width:100vw;height:100vh;overflow:hidden;background:var(--bg)}
|
||||
.slide{
|
||||
position:absolute;inset:0;
|
||||
display:flex;flex-direction:column;justify-content:center;
|
||||
padding:72px 96px;
|
||||
box-sizing:border-box;
|
||||
opacity:0;pointer-events:none;
|
||||
transition:opacity .5s var(--ease), transform .5s var(--ease);
|
||||
transform:translateX(30px);
|
||||
overflow:hidden;
|
||||
}
|
||||
.slide.is-active{opacity:1;pointer-events:auto;transform:translateX(0);z-index:2}
|
||||
.slide.is-prev{transform:translateX(-30px)}
|
||||
|
||||
/* single-page standalone (used when a layout file is opened directly) */
|
||||
body.single .slide{position:relative;width:100vw;height:100vh;opacity:1;transform:none;pointer-events:auto}
|
||||
|
||||
/* ================= TYPOGRAPHY ================= */
|
||||
.eyebrow{font-size:13px;font-weight:500;letter-spacing:.16em;text-transform:uppercase;color:var(--text-3)}
|
||||
.kicker{font-size:14px;font-weight:600;color:var(--accent);letter-spacing:.08em;text-transform:uppercase}
|
||||
h1.title,.h1{font-family:var(--font-display);font-size:72px;line-height:1.05;font-weight:800;letter-spacing:var(--letter-tight);margin:0 0 18px;color:var(--text-1)}
|
||||
h2.title,.h2{font-family:var(--font-display);font-size:54px;line-height:1.1;font-weight:700;letter-spacing:var(--letter-tight);margin:0 0 14px}
|
||||
h3,.h3{font-size:32px;line-height:1.2;font-weight:600;letter-spacing:var(--letter-normal);margin:0 0 10px}
|
||||
h4,.h4{font-size:22px;line-height:1.3;font-weight:600;margin:0 0 8px}
|
||||
.lede{font-size:22px;line-height:1.55;color:var(--text-2);font-weight:300;max-width:62ch}
|
||||
.dim{color:var(--text-2)}
|
||||
.dim2{color:var(--text-3)}
|
||||
.mono{font-family:var(--font-mono)}
|
||||
.serif{font-family:var(--font-serif)}
|
||||
.gradient-text{background:var(--grad);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;color:transparent}
|
||||
|
||||
/* ================= LAYOUT PRIMITIVES ================= */
|
||||
.stack>*+*{margin-top:14px}
|
||||
.row{display:flex;gap:24px;align-items:center}
|
||||
.row.wrap{flex-wrap:wrap}
|
||||
.grid{display:grid;gap:24px}
|
||||
.g2{grid-template-columns:repeat(2,1fr)}
|
||||
.g3{grid-template-columns:repeat(3,1fr)}
|
||||
.g4{grid-template-columns:repeat(4,1fr)}
|
||||
.center{display:flex;align-items:center;justify-content:center;text-align:center}
|
||||
.fill{flex:1}
|
||||
.sp-t{padding-top:24px}.sp-b{padding-bottom:24px}
|
||||
.mt-s{margin-top:8px}.mt-m{margin-top:18px}.mt-l{margin-top:32px}
|
||||
.mb-s{margin-bottom:8px}.mb-m{margin-bottom:18px}.mb-l{margin-bottom:32px}
|
||||
|
||||
/* ================= CARDS ================= */
|
||||
.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);
|
||||
padding:26px 28px;box-shadow:var(--shadow);position:relative;overflow:hidden}
|
||||
.card-soft{background:var(--surface-2);border:1px solid var(--border)}
|
||||
.card-outline{background:transparent;border:1.5px solid var(--border-strong);box-shadow:none}
|
||||
.card-accent{background:var(--surface);border-top:3px solid var(--accent)}
|
||||
.card-hover{transition:transform .3s var(--ease),box-shadow .3s var(--ease)}
|
||||
.card-hover:hover{transform:translateY(-4px);box-shadow:var(--shadow-lg)}
|
||||
|
||||
.pill{display:inline-block;padding:4px 12px;border-radius:999px;font-size:12px;font-weight:500;
|
||||
background:var(--surface-2);color:var(--text-2);border:1px solid var(--border)}
|
||||
.pill-accent{background:color-mix(in srgb,var(--accent) 12%,transparent);color:var(--accent);border-color:color-mix(in srgb,var(--accent) 28%,transparent)}
|
||||
|
||||
/* ================= BARS / DIVIDERS ================= */
|
||||
.divider{height:1px;background:var(--border);width:100%}
|
||||
.divider-accent{height:3px;width:72px;background:var(--accent);border-radius:2px}
|
||||
|
||||
/* ================= CHROME (header/footer/progress) ================= */
|
||||
.deck-header{position:absolute;top:24px;left:40px;right:40px;display:flex;align-items:center;justify-content:space-between;
|
||||
font-size:12px;color:var(--text-3);letter-spacing:.12em;text-transform:uppercase;z-index:10;pointer-events:none}
|
||||
.deck-footer{position:absolute;bottom:24px;left:40px;right:40px;display:flex;align-items:center;justify-content:space-between;
|
||||
font-size:12px;color:var(--text-3);z-index:10;pointer-events:none}
|
||||
.slide-number::before{content:attr(data-current)}
|
||||
.slide-number::after{content:" / " attr(data-total)}
|
||||
.progress-bar{position:fixed;left:0;right:0;bottom:0;height:3px;background:transparent;z-index:20}
|
||||
.progress-bar > span{display:block;height:100%;width:0;background:var(--accent);transition:width .3s var(--ease)}
|
||||
|
||||
/* ================= PRESENTER / OVERVIEW ================= */
|
||||
.notes{display:none!important}
|
||||
.notes-overlay{position:fixed;inset:auto 0 0 0;max-height:42vh;background:rgba(20,22,30,.95);color:#e8ebf4;
|
||||
padding:20px 32px;font-size:16px;line-height:1.6;border-top:1px solid rgba(255,255,255,.1);transform:translateY(100%);
|
||||
transition:transform .3s var(--ease);z-index:40;overflow:auto;font-family:var(--font-sans)}
|
||||
.notes-overlay.open{transform:translateY(0)}
|
||||
.overview{position:fixed;inset:0;background:rgba(10,12,18,.92);backdrop-filter:blur(12px);z-index:50;
|
||||
display:none;padding:40px;overflow:auto}
|
||||
.overview.open{display:grid;grid-template-columns:repeat(4,1fr);gap:20px;align-content:start}
|
||||
.overview .thumb{background:var(--surface);border:1px solid var(--border);border-radius:12px;
|
||||
aspect-ratio:16/9;overflow:hidden;cursor:pointer;position:relative;color:var(--text-1);padding:16px;
|
||||
font-size:11px;transition:transform .2s var(--ease)}
|
||||
.overview .thumb:hover{transform:scale(1.04)}
|
||||
.overview .thumb .n{position:absolute;top:8px;left:10px;font-weight:700;font-size:14px;color:var(--text-3)}
|
||||
.overview .thumb .t{position:absolute;bottom:10px;left:14px;right:14px;font-weight:600;color:var(--text-1)}
|
||||
|
||||
/* ================= PRESENTER VIEW ================= */
|
||||
/* Presenter view opens in a separate popup window (S key).
|
||||
* All presenter styles are self-contained in the popup HTML generated by runtime.js.
|
||||
* The audience window (this file) is NOT affected — it stays as normal deck view.
|
||||
* Only the .notes class below is needed to hide speaker notes from audience. */
|
||||
|
||||
/* ================= UTILITY ================= */
|
||||
.hidden{display:none!important}
|
||||
.nowrap{white-space:nowrap}
|
||||
.tr{text-align:right}.tc{text-align:center}.tl{text-align:left}
|
||||
.uppercase{text-transform:uppercase;letter-spacing:.12em}
|
||||
|
||||
/* ================= PRINT ================= */
|
||||
@media print{
|
||||
.slide{position:relative;opacity:1!important;transform:none!important;page-break-after:always;height:100vh}
|
||||
.deck-header,.deck-footer,.progress-bar,.notes-overlay,.overview{display:none!important}
|
||||
}
|
||||
|
||||
</style>
|
||||
<style>/* xhs-pastel-card — 柔和马卡龙大色块封面风 */
|
||||
.tpl-xhs-pastel-card{
|
||||
--xp-bg:#fef8f1;
|
||||
--xp-ink:#2a2340;
|
||||
--xp-ink2:#5b5470;
|
||||
--xp-muted:#9089a8;
|
||||
--xp-peach:#ffd8c2;
|
||||
--xp-peach-d:#f48b5c;
|
||||
--xp-mint:#c8ecd8;
|
||||
--xp-mint-d:#2e9d70;
|
||||
--xp-sky:#c9dcfb;
|
||||
--xp-sky-d:#4e7ed6;
|
||||
--xp-lilac:#ddd0f5;
|
||||
--xp-lilac-d:#7b5dc4;
|
||||
--xp-lemon:#fdf0b2;
|
||||
--xp-lemon-d:#c8910a;
|
||||
--xp-rose:#fcd0dd;
|
||||
--xp-rose-d:#c94673;
|
||||
background:var(--xp-bg);
|
||||
color:var(--xp-ink);
|
||||
font-family:'Playfair Display','Noto Serif SC','Inter','Noto Sans SC',Georgia,serif;
|
||||
}
|
||||
.tpl-xhs-pastel-card .slide{background:var(--xp-bg);color:var(--xp-ink);padding:76px 90px}
|
||||
.tpl-xhs-pastel-card .xp-blob{position:absolute;border-radius:50%;filter:blur(2px);opacity:.85;z-index:0}
|
||||
.tpl-xhs-pastel-card .xp-blob.b1{width:420px;height:420px;background:radial-gradient(circle,var(--xp-peach),transparent 70%);top:-8%;right:-6%}
|
||||
.tpl-xhs-pastel-card .xp-blob.b2{width:360px;height:360px;background:radial-gradient(circle,var(--xp-lilac),transparent 72%);bottom:-10%;left:-8%}
|
||||
.tpl-xhs-pastel-card .xp-blob.b3{width:260px;height:260px;background:radial-gradient(circle,var(--xp-mint),transparent 72%);top:40%;right:20%}
|
||||
.tpl-xhs-pastel-card .slide > *{position:relative;z-index:2}
|
||||
.tpl-xhs-pastel-card .xp-topbar{display:flex;justify-content:space-between;align-items:center;margin-bottom:22px;font-family:'Inter','Noto Sans SC',sans-serif}
|
||||
.tpl-xhs-pastel-card .xp-chip{display:inline-flex;align-items:center;gap:10px;padding:8px 18px;border-radius:999px;background:#fff;border:1.5px solid rgba(42,35,64,.1);font-size:13px;font-weight:600;letter-spacing:.08em;color:var(--xp-ink2);text-transform:uppercase}
|
||||
.tpl-xhs-pastel-card .xp-chip::before{content:'';width:9px;height:9px;border-radius:50%;background:var(--xp-peach-d)}
|
||||
.tpl-xhs-pastel-card .xp-chip.mint::before{background:var(--xp-mint-d)}
|
||||
.tpl-xhs-pastel-card .xp-chip.sky::before{background:var(--xp-sky-d)}
|
||||
.tpl-xhs-pastel-card .xp-chip.lilac::before{background:var(--xp-lilac-d)}
|
||||
.tpl-xhs-pastel-card .xp-chip.rose::before{background:var(--xp-rose-d)}
|
||||
.tpl-xhs-pastel-card .xp-page{font-family:'Inter',sans-serif;font-size:13px;color:var(--xp-muted);letter-spacing:.12em;font-weight:600}
|
||||
.tpl-xhs-pastel-card .xp-kicker{font-family:'Inter',sans-serif;font-size:14px;font-weight:700;letter-spacing:.18em;text-transform:uppercase;color:var(--xp-peach-d);margin-bottom:14px}
|
||||
.tpl-xhs-pastel-card .xp-h1{font-size:96px;font-weight:900;line-height:1.05;letter-spacing:-2px;margin:0 0 18px;color:var(--xp-ink);font-family:'Playfair Display','Noto Serif SC',serif}
|
||||
.tpl-xhs-pastel-card .xp-h1 em{font-style:italic;color:var(--xp-peach-d);font-family:'Playfair Display',serif}
|
||||
.tpl-xhs-pastel-card .xp-h1 .rose{color:var(--xp-rose-d);font-style:italic}
|
||||
.tpl-xhs-pastel-card .xp-h1 .mint{color:var(--xp-mint-d);font-style:italic}
|
||||
.tpl-xhs-pastel-card .xp-h2{font-size:60px;font-weight:800;line-height:1.1;letter-spacing:-1px;margin:0 0 14px;font-family:'Playfair Display','Noto Serif SC',serif}
|
||||
.tpl-xhs-pastel-card .xp-sub{font-family:'Inter','Noto Sans SC',sans-serif;font-size:21px;line-height:1.6;color:var(--xp-ink2);max-width:800px;font-weight:400}
|
||||
.tpl-xhs-pastel-card .xp-card{border-radius:28px;padding:30px 34px;background:#fff;box-shadow:0 14px 40px rgba(42,35,64,.08);position:relative;overflow:hidden}
|
||||
.tpl-xhs-pastel-card .xp-card.peach{background:var(--xp-peach)}
|
||||
.tpl-xhs-pastel-card .xp-card.mint{background:var(--xp-mint)}
|
||||
.tpl-xhs-pastel-card .xp-card.sky{background:var(--xp-sky)}
|
||||
.tpl-xhs-pastel-card .xp-card.lilac{background:var(--xp-lilac)}
|
||||
.tpl-xhs-pastel-card .xp-card.lemon{background:var(--xp-lemon)}
|
||||
.tpl-xhs-pastel-card .xp-card.rose{background:var(--xp-rose)}
|
||||
.tpl-xhs-pastel-card .xp-card .xp-num{font-family:'Playfair Display',serif;font-size:68px;font-weight:900;font-style:italic;line-height:1;opacity:.85}
|
||||
.tpl-xhs-pastel-card .xp-card h4{font-size:22px;font-weight:800;margin:8px 0;font-family:'Inter','Noto Sans SC',sans-serif}
|
||||
.tpl-xhs-pastel-card .xp-card p{font-family:'Inter','Noto Sans SC',sans-serif;font-size:15px;line-height:1.55;color:var(--xp-ink2)}
|
||||
.tpl-xhs-pastel-card .xp-grid-2{display:grid;grid-template-columns:1fr 1fr;gap:20px;margin-top:26px}
|
||||
.tpl-xhs-pastel-card .xp-grid-3{display:grid;grid-template-columns:1fr 1fr 1fr;gap:18px;margin-top:26px}
|
||||
.tpl-xhs-pastel-card .xp-grid-4{display:grid;grid-template-columns:repeat(4,1fr);gap:16px;margin-top:24px}
|
||||
.tpl-xhs-pastel-card .xp-hero-card{background:#fff;border-radius:36px;padding:40px 46px;margin-top:28px;box-shadow:0 20px 50px rgba(42,35,64,.1)}
|
||||
.tpl-xhs-pastel-card .xp-quote{font-family:'Playfair Display','Noto Serif SC',serif;font-size:40px;font-weight:800;font-style:italic;line-height:1.3;color:var(--xp-ink)}
|
||||
.tpl-xhs-pastel-card .xp-quote::before{content:'“';font-size:100px;line-height:.8;display:block;color:var(--xp-peach-d);opacity:.7}
|
||||
.tpl-xhs-pastel-card .xp-footer{position:absolute;left:90px;right:90px;bottom:40px;display:flex;justify-content:space-between;font-family:'Inter',sans-serif;font-size:12px;color:var(--xp-muted);letter-spacing:.1em}
|
||||
.tpl-xhs-pastel-card .xp-divider{width:90px;height:4px;background:linear-gradient(90deg,var(--xp-peach-d),var(--xp-rose-d));border-radius:2px;margin:20px 0}
|
||||
.tpl-xhs-pastel-card .xp-codebox{background:#2a2340;color:#fef8f1;border-radius:24px;padding:26px 30px;font-family:'JetBrains Mono',monospace;font-size:14px;line-height:1.85;margin-top:22px}
|
||||
.tpl-xhs-pastel-card .xp-codebox .cm{color:#9089a8}
|
||||
.tpl-xhs-pastel-card .xp-codebox .kw{color:#ffc6a0}
|
||||
.tpl-xhs-pastel-card .xp-codebox .st{color:#c8ecd8}
|
||||
.tpl-xhs-pastel-card .xp-codebox .hl{color:#fcd0dd;font-weight:700}
|
||||
|
||||
</style>
|
||||
<style>
|
||||
/* Static-preview fallback (runtime.js is absent — keep every slide visible) */
|
||||
.deck{height:auto;min-height:100vh;overflow:visible}
|
||||
.slide{position:relative;inset:auto;opacity:1;pointer-events:auto;transform:none;height:100vh;page-break-after:always}
|
||||
.deck-header,.deck-footer,.slide-number,.progress-bar,.notes-overlay,.overview{pointer-events:none}
|
||||
.notes{display:none!important}
|
||||
</style></head>
|
||||
<body class="tpl-xhs-pastel-card">
|
||||
<div class="deck">
|
||||
|
||||
<!-- 1. COVER -->
|
||||
<section class="slide is-active">
|
||||
<div class="xp-blob b1"></div>
|
||||
<div class="xp-blob b2"></div>
|
||||
<div class="xp-blob b3"></div>
|
||||
<div class="xp-topbar"><div class="xp-chip">A soft manifesto</div><div class="xp-page">01 · 08</div></div>
|
||||
<div class="xp-kicker">Living With AI · 2026</div>
|
||||
<h1 class="xp-h1">放慢一点,<br>让 <em>AI</em> 帮你<br>过一种 <span class="rose">更温柔</span><br>的生活</h1>
|
||||
<div class="xp-divider"></div>
|
||||
<p class="xp-sub">这不是一份效率指南。这是一份「怎么用 AI 少做一些事」的清单 —— 把挤出来的 4 小时还给你自己。</p>
|
||||
<div class="xp-footer"><span>by lewis · pastel edition</span><span>cover</span></div>
|
||||
</section>
|
||||
|
||||
<!-- 2. SECTION -->
|
||||
<section class="slide">
|
||||
<div class="xp-blob b2"></div>
|
||||
<div class="xp-blob b3"></div>
|
||||
<div class="xp-topbar"><div class="xp-chip mint">Chapter one</div><div class="xp-page">02 · 08</div></div>
|
||||
<div style="margin:auto 0">
|
||||
<div class="xp-kicker">先问自己</div>
|
||||
<h1 class="xp-h1" style="font-size:120px">什么事<br>是你 <span class="mint">其实不想做</span> 的?</h1>
|
||||
<p class="xp-sub">不是「不得不做」,是「做的时候灵魂在叹气」。</p>
|
||||
</div>
|
||||
<div class="xp-footer"><span>section · chapter 1</span><span>02 · 08</span></div>
|
||||
</section>
|
||||
|
||||
<!-- 3. CONTENT 2x2 pastel cards -->
|
||||
<section class="slide">
|
||||
<div class="xp-blob b1"></div>
|
||||
<div class="xp-topbar"><div class="xp-chip rose">Four little escapes</div><div class="xp-page">03 · 08</div></div>
|
||||
<h2 class="xp-h2">四件可以<br>完全交给 <em>AI</em> 的小事</h2>
|
||||
<div class="xp-grid-2">
|
||||
<div class="xp-card peach"><div class="xp-num">01</div><h4>回复那种「收到」邮件</h4><p>它们不需要你思考。让 AI 按你的语气自动处理,一周省 40 分钟。</p></div>
|
||||
<div class="xp-card mint"><div class="xp-num">02</div><h4>订餐厅、改签、查路线</h4><p>一句话外包出去。你只负责选最后选项,不负责翻十个 app。</p></div>
|
||||
<div class="xp-card sky"><div class="xp-num">03</div><h4>把会议录音变成行动项</h4><p>录音 → 摘要 → todo 一键完成。你只需要确认和签字。</p></div>
|
||||
<div class="xp-card lilac"><div class="xp-num">04</div><h4>整理上周拍的 300 张照片</h4><p>按事件分类、挑 10 张精选、写图说。整理档案这件事终于被自动化了。</p></div>
|
||||
</div>
|
||||
<div class="xp-footer"><span>content · 2x2</span><span>03 · 08</span></div>
|
||||
</section>
|
||||
|
||||
<!-- 4. QUOTE -->
|
||||
<section class="slide">
|
||||
<div class="xp-blob b3"></div>
|
||||
<div class="xp-blob b2"></div>
|
||||
<div class="xp-topbar"><div class="xp-chip lilac">A small pause</div><div class="xp-page">04 · 08</div></div>
|
||||
<div class="xp-hero-card">
|
||||
<p class="xp-quote">效率工具的终点,不是<em> 做更多</em>,<br>而是 <em>有资格做更少</em>。</p>
|
||||
<div class="xp-divider"></div>
|
||||
<p class="xp-sub">当你把「收到」邮件、订餐、行程、照片整理都交出去,你才会惊讶地发现 —— 原来一周有 4 个小时是空的。</p>
|
||||
</div>
|
||||
<div class="xp-footer"><span>quote</span><span>04 · 08</span></div>
|
||||
</section>
|
||||
|
||||
<!-- 5. CODE / PROMPT -->
|
||||
<section class="slide">
|
||||
<div class="xp-blob b1"></div>
|
||||
<div class="xp-topbar"><div class="xp-chip">My auto-reply prompt</div><div class="xp-page">05 · 08</div></div>
|
||||
<h2 class="xp-h2">把「<em>收到邮件</em>」<br>自动化的 <span class="rose">一段 prompt</span></h2>
|
||||
<pre class="xp-codebox"><span class="cm"># auto-reply skill</span>
|
||||
<span class="kw">when</span> email matches <span class="st">"收到 / 好的 / 确认 / 收到谢谢"</span>:
|
||||
reply:
|
||||
tone: <span class="st">"温柔,简短,不要太商业"</span>
|
||||
max_lines: <span class="hl">2</span>
|
||||
sign_with: <span class="st">"— Lewis"</span>
|
||||
|
||||
<span class="kw">always_skip</span>:
|
||||
- from: [<span class="st">"家人"</span>, <span class="st">"伴侣"</span>, <span class="st">"亲密朋友"</span>]
|
||||
- contains: [<span class="st">"紧急"</span>, <span class="st">"合同"</span>, <span class="st">"付款"</span>]
|
||||
|
||||
<span class="cm"># 一周省 38 分钟,测过</span></pre>
|
||||
<div class="xp-footer"><span>content · prompt</span><span>05 · 08</span></div>
|
||||
</section>
|
||||
|
||||
<!-- 6. CHART — time donut -->
|
||||
<section class="slide">
|
||||
<div class="xp-blob b2"></div>
|
||||
<div class="xp-topbar"><div class="xp-chip mint">Your week, rebuilt</div><div class="xp-page">06 · 08</div></div>
|
||||
<h2 class="xp-h2">一周 4 小时 <span class="mint">还给自己</span></h2>
|
||||
<div style="display:flex;align-items:center;gap:60px;margin-top:30px">
|
||||
<svg viewBox="0 0 260 260" style="width:300px;flex-shrink:0">
|
||||
<circle cx="130" cy="130" r="100" fill="none" stroke="#fef0e4" stroke-width="40"/>
|
||||
<!-- email 12% -->
|
||||
<circle cx="130" cy="130" r="100" fill="none" stroke="#f48b5c" stroke-width="40" stroke-dasharray="75 628" stroke-dashoffset="0" transform="rotate(-90 130 130)"/>
|
||||
<!-- logistics 18% -->
|
||||
<circle cx="130" cy="130" r="100" fill="none" stroke="#2e9d70" stroke-width="40" stroke-dasharray="113 628" stroke-dashoffset="-75" transform="rotate(-90 130 130)"/>
|
||||
<!-- meetings 14% -->
|
||||
<circle cx="130" cy="130" r="100" fill="none" stroke="#4e7ed6" stroke-width="40" stroke-dasharray="88 628" stroke-dashoffset="-188" transform="rotate(-90 130 130)"/>
|
||||
<!-- photos 6% -->
|
||||
<circle cx="130" cy="130" r="100" fill="none" stroke="#7b5dc4" stroke-width="40" stroke-dasharray="38 628" stroke-dashoffset="-276" transform="rotate(-90 130 130)"/>
|
||||
<text x="130" y="130" text-anchor="middle" font-family="Playfair Display" font-size="44" font-weight="900" fill="#2a2340">4h</text>
|
||||
<text x="130" y="156" text-anchor="middle" font-family="Inter" font-size="12" fill="#9089a8">per week saved</text>
|
||||
</svg>
|
||||
<div style="flex:1">
|
||||
<div class="xp-grid-2" style="grid-template-columns:1fr;gap:12px;margin-top:0">
|
||||
<div class="xp-card peach" style="padding:14px 20px;display:flex;align-items:center;gap:14px"><div style="width:14px;height:14px;border-radius:50%;background:var(--xp-peach-d)"></div><div><h4 style="margin:0;font-size:17px">48 min · 邮件</h4></div></div>
|
||||
<div class="xp-card mint" style="padding:14px 20px;display:flex;align-items:center;gap:14px"><div style="width:14px;height:14px;border-radius:50%;background:var(--xp-mint-d)"></div><div><h4 style="margin:0;font-size:17px">72 min · 订/改/查</h4></div></div>
|
||||
<div class="xp-card sky" style="padding:14px 20px;display:flex;align-items:center;gap:14px"><div style="width:14px;height:14px;border-radius:50%;background:var(--xp-sky-d)"></div><div><h4 style="margin:0;font-size:17px">56 min · 会议摘要</h4></div></div>
|
||||
<div class="xp-card lilac" style="padding:14px 20px;display:flex;align-items:center;gap:14px"><div style="width:14px;height:14px;border-radius:50%;background:var(--xp-lilac-d)"></div><div><h4 style="margin:0;font-size:17px">24 min · 照片整理</h4></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="xp-footer"><span>chart · donut</span><span>06 · 08</span></div>
|
||||
</section>
|
||||
|
||||
<!-- 7. CTA -->
|
||||
<section class="slide">
|
||||
<div class="xp-blob b1"></div>
|
||||
<div class="xp-blob b3"></div>
|
||||
<div class="xp-topbar"><div class="xp-chip rose">This weekend</div><div class="xp-page">07 · 08</div></div>
|
||||
<h2 class="xp-h2">这周末,<br>先给自己 <em>放一个小假</em></h2>
|
||||
<div class="xp-grid-3">
|
||||
<div class="xp-card lemon"><div class="xp-num">☕</div><h4>Saturday morning</h4><p>挑一个你最烦的小事,写 prompt,让它从此不再烦你。</p></div>
|
||||
<div class="xp-card peach"><div class="xp-num">🌸</div><h4>Saturday afternoon</h4><p>去散步。什么都不带。AI 在家帮你看着消息。</p></div>
|
||||
<div class="xp-card sky"><div class="xp-num">🌙</div><h4>Sunday night</h4><p>复盘:哪 4 小时是真的空的?下周继续。</p></div>
|
||||
</div>
|
||||
<div class="xp-footer"><span>cta</span><span>07 · 08</span></div>
|
||||
</section>
|
||||
|
||||
<!-- 8. THANKS -->
|
||||
<section class="slide">
|
||||
<div class="xp-blob b2"></div>
|
||||
<div style="margin:auto 0;text-align:center">
|
||||
<div class="xp-kicker" style="text-align:center">thanks for reading</div>
|
||||
<h1 class="xp-h1" style="font-size:160px;text-align:center">谢谢 <em>·</em> thanks</h1>
|
||||
<div class="xp-divider" style="margin:24px auto"></div>
|
||||
<p class="xp-sub" style="margin:0 auto">如果你也想过更温柔的一周,评论区跟我说说你打算把哪一件事先交出去 ♡</p>
|
||||
</div>
|
||||
<div class="xp-footer"><span>end</span><span>08 · 08</span></div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
79
skills/html-ppt-xhs-post/SKILL.md
Normal file
79
skills/html-ppt-xhs-post/SKILL.md
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
---
|
||||
name: html-ppt-xhs-post
|
||||
description: 小红书 / Instagram 风 9 页 3:4 竖版图文(810×1080)— 暖色 pastel、虚线 sticker 卡片、底部页码点点。用于发小红书图文、Instagram carousel、品牌种草内容。
|
||||
triggers:
|
||||
- "小红书"
|
||||
- "xhs"
|
||||
- "xhs post"
|
||||
- "xiaohongshu"
|
||||
- "图文"
|
||||
- "instagram carousel"
|
||||
- "种草"
|
||||
od:
|
||||
mode: deck
|
||||
scenario: marketing
|
||||
featured: 24
|
||||
upstream: "https://github.com/lewislulu/html-ppt-skill"
|
||||
preview:
|
||||
type: html
|
||||
entry: index.html
|
||||
design_system:
|
||||
requires: false
|
||||
speaker_notes: true
|
||||
animations: true
|
||||
example_prompt: "帮我用 html-ppt-xhs-post 模板做一组 9 张小红书图文(3:4 竖版,810×1080)。先告诉我主题,然后帮我把封面 + 7 页内容 + 结尾 CTA 排好,每页一句标题 + 一段正文 + 关键词 sticker。"
|
||||
---
|
||||
# HTML PPT · 小红书 图文
|
||||
|
||||
A focused entry point into the [`html-ppt`](../html-ppt/SKILL.md) master skill that lands the user directly on the **`xhs-post`** full-deck template.
|
||||
|
||||
## When this card is picked
|
||||
|
||||
The Examples gallery wires "Use this prompt" to the example_prompt above. When you accept that prompt, this card is the right pick if the user wants exactly the visual identity of `xhs-post` (see the upstream [full-decks catalog](../html-ppt/references/full-decks.md) for screenshots and rationale).
|
||||
|
||||
## How to author the deck
|
||||
|
||||
1. **Read the master skill first.** All authoring rules live in
|
||||
[`skills/html-ppt/SKILL.md`](../html-ppt/SKILL.md) — content/audience checklist,
|
||||
token rules, layout reuse, presenter mode, the keyboard runtime, and the
|
||||
"never put presenter-only text on the slide" rule.
|
||||
2. **Start from the matching template folder:**
|
||||
`skills/html-ppt/templates/full-decks/xhs-post/` — copy `index.html` and
|
||||
`style.css` into the project, keep the `.tpl-xhs-post` body class.
|
||||
3. **Bring the shared runtime with the template.** The upstream
|
||||
`index.html` links the shared CSS/JS via `../../../assets/...` because it
|
||||
sits three folders deep inside `skills/html-ppt/templates/full-decks/`.
|
||||
Once you copy `index.html` into the project, those parent-relative URLs
|
||||
no longer resolve and `base.css`, `animations.css`, and `runtime.js`
|
||||
will 404 — meaning the deck never activates and slide navigation is
|
||||
dead. Pick one of these two recipes per project:
|
||||
- **Recipe A — copy + rewrite (preferred):** copy
|
||||
`skills/html-ppt/assets/fonts.css`, `skills/html-ppt/assets/base.css`,
|
||||
`skills/html-ppt/assets/animations/animations.css`, and
|
||||
`skills/html-ppt/assets/runtime.js` into a project-local
|
||||
`assets/` (with `assets/animations/animations.css`), then rewrite the
|
||||
four `<link>`/`<script>` tags in `index.html` from
|
||||
`../../../assets/...` to the matching project-local paths
|
||||
(`assets/fonts.css`, `assets/base.css`,
|
||||
`assets/animations/animations.css`, `assets/runtime.js`).
|
||||
- **Recipe B — inline:** read the same four files and replace each
|
||||
`<link rel="stylesheet" href="../../../assets/...">` with a
|
||||
`<style>...</style>` containing the file's contents, and the
|
||||
`<script src="../../../assets/runtime.js">` with a
|
||||
`<script>...</script>` containing `runtime.js`. Yields a single
|
||||
self-contained `index.html`.
|
||||
Either way, do not ship the upstream `../../../assets/...` URLs
|
||||
verbatim into a project artifact — they only work in-tree.
|
||||
4. **Pick a theme.** Default tokens look fine; if the user wants a different
|
||||
feel, swap in any of the 36 themes from `skills/html-ppt/assets/themes/*.css`
|
||||
via `<link id="theme-link">` and let `T` cycle.
|
||||
5. **Replace demo content, not classes.** The `.tpl-xhs-post` scoped CSS only
|
||||
recognises the structural classes shipped in the template — keep them.
|
||||
6. **Speaker notes go inside `<aside class="notes">` or `<div class="notes">`** — never as visible text on the slide.
|
||||
|
||||
## Attribution
|
||||
|
||||
Visual system, layouts, themes and the runtime keyboard model come from
|
||||
the upstream MIT-licensed [`lewislulu/html-ppt-skill`](https://github.com/lewislulu/html-ppt-skill). The
|
||||
LICENSE file ships at `skills/html-ppt/LICENSE`; please keep it in place when
|
||||
redistributing.
|
||||
487
skills/html-ppt-xhs-post/example.html
Normal file
487
skills/html-ppt-xhs-post/example.html
Normal file
|
|
@ -0,0 +1,487 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>每天只睡 6h 还精神?· 小红书图文</title>
|
||||
<style>/* html-ppt :: shared webfonts */
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@200;300;400;500;600;700;800;900&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@200;300;400;500;600;700;900&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@300;400;600;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,600;0,800;1,400&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@300;400;500;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Archivo+Black&display=swap');
|
||||
|
||||
</style>
|
||||
<style>/* html-ppt :: base.css — reset + shared tokens + layout primitives */
|
||||
/* Default tokens. Themes in assets/themes/*.css override the :root block. */
|
||||
:root {
|
||||
--bg: #ffffff;
|
||||
--bg-soft: #f7f7f8;
|
||||
--surface: #ffffff;
|
||||
--surface-2: #f2f2f4;
|
||||
--border: rgba(0,0,0,.08);
|
||||
--border-strong: rgba(0,0,0,.16);
|
||||
--text-1: #111216;
|
||||
--text-2: #55596a;
|
||||
--text-3: #8a8f9e;
|
||||
--accent: #3b6cff;
|
||||
--accent-2: #7a5cff;
|
||||
--accent-3: #ff5c8a;
|
||||
--good: #1aaf6c;
|
||||
--warn: #f5a524;
|
||||
--bad: #e0445a;
|
||||
--grad: linear-gradient(135deg,#3b6cff,#7a5cff 55%,#ff5c8a);
|
||||
--grad-soft: linear-gradient(135deg,#eef2ff,#f5ecff 55%,#ffeef5);
|
||||
--radius: 18px;
|
||||
--radius-sm: 12px;
|
||||
--radius-lg: 26px;
|
||||
--shadow: 0 10px 30px rgba(18,24,40,.08), 0 2px 6px rgba(18,24,40,.04);
|
||||
--shadow-lg: 0 24px 60px rgba(18,24,40,.14), 0 6px 16px rgba(18,24,40,.06);
|
||||
--font-sans: 'Inter','Noto Sans SC',-apple-system,BlinkMacSystemFont,Helvetica,Arial,sans-serif;
|
||||
--font-serif: 'Playfair Display','Noto Serif SC',Georgia,serif;
|
||||
--font-mono: 'JetBrains Mono','IBM Plex Mono',SFMono-Regular,Menlo,monospace;
|
||||
--font-display: var(--font-sans);
|
||||
--letter-tight: -.03em;
|
||||
--letter-normal: -.01em;
|
||||
--ease: cubic-bezier(.4,0,.2,1);
|
||||
}
|
||||
|
||||
*,*::before,*::after{box-sizing:border-box}
|
||||
html,body{margin:0;padding:0;background:var(--bg);color:var(--text-1);
|
||||
font-family:var(--font-sans);font-weight:400;line-height:1.6;
|
||||
-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;
|
||||
letter-spacing:var(--letter-normal)}
|
||||
img,svg,video{max-width:100%;display:block}
|
||||
a{color:var(--accent);text-decoration:none}
|
||||
a:hover{text-decoration:underline}
|
||||
code,kbd,pre,samp{font-family:var(--font-mono)}
|
||||
|
||||
/* ================= SLIDE SYSTEM ================= */
|
||||
.deck{position:relative;width:100vw;height:100vh;overflow:hidden;background:var(--bg)}
|
||||
.slide{
|
||||
position:absolute;inset:0;
|
||||
display:flex;flex-direction:column;justify-content:center;
|
||||
padding:72px 96px;
|
||||
box-sizing:border-box;
|
||||
opacity:0;pointer-events:none;
|
||||
transition:opacity .5s var(--ease), transform .5s var(--ease);
|
||||
transform:translateX(30px);
|
||||
overflow:hidden;
|
||||
}
|
||||
.slide.is-active{opacity:1;pointer-events:auto;transform:translateX(0);z-index:2}
|
||||
.slide.is-prev{transform:translateX(-30px)}
|
||||
|
||||
/* single-page standalone (used when a layout file is opened directly) */
|
||||
body.single .slide{position:relative;width:100vw;height:100vh;opacity:1;transform:none;pointer-events:auto}
|
||||
|
||||
/* ================= TYPOGRAPHY ================= */
|
||||
.eyebrow{font-size:13px;font-weight:500;letter-spacing:.16em;text-transform:uppercase;color:var(--text-3)}
|
||||
.kicker{font-size:14px;font-weight:600;color:var(--accent);letter-spacing:.08em;text-transform:uppercase}
|
||||
h1.title,.h1{font-family:var(--font-display);font-size:72px;line-height:1.05;font-weight:800;letter-spacing:var(--letter-tight);margin:0 0 18px;color:var(--text-1)}
|
||||
h2.title,.h2{font-family:var(--font-display);font-size:54px;line-height:1.1;font-weight:700;letter-spacing:var(--letter-tight);margin:0 0 14px}
|
||||
h3,.h3{font-size:32px;line-height:1.2;font-weight:600;letter-spacing:var(--letter-normal);margin:0 0 10px}
|
||||
h4,.h4{font-size:22px;line-height:1.3;font-weight:600;margin:0 0 8px}
|
||||
.lede{font-size:22px;line-height:1.55;color:var(--text-2);font-weight:300;max-width:62ch}
|
||||
.dim{color:var(--text-2)}
|
||||
.dim2{color:var(--text-3)}
|
||||
.mono{font-family:var(--font-mono)}
|
||||
.serif{font-family:var(--font-serif)}
|
||||
.gradient-text{background:var(--grad);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;color:transparent}
|
||||
|
||||
/* ================= LAYOUT PRIMITIVES ================= */
|
||||
.stack>*+*{margin-top:14px}
|
||||
.row{display:flex;gap:24px;align-items:center}
|
||||
.row.wrap{flex-wrap:wrap}
|
||||
.grid{display:grid;gap:24px}
|
||||
.g2{grid-template-columns:repeat(2,1fr)}
|
||||
.g3{grid-template-columns:repeat(3,1fr)}
|
||||
.g4{grid-template-columns:repeat(4,1fr)}
|
||||
.center{display:flex;align-items:center;justify-content:center;text-align:center}
|
||||
.fill{flex:1}
|
||||
.sp-t{padding-top:24px}.sp-b{padding-bottom:24px}
|
||||
.mt-s{margin-top:8px}.mt-m{margin-top:18px}.mt-l{margin-top:32px}
|
||||
.mb-s{margin-bottom:8px}.mb-m{margin-bottom:18px}.mb-l{margin-bottom:32px}
|
||||
|
||||
/* ================= CARDS ================= */
|
||||
.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);
|
||||
padding:26px 28px;box-shadow:var(--shadow);position:relative;overflow:hidden}
|
||||
.card-soft{background:var(--surface-2);border:1px solid var(--border)}
|
||||
.card-outline{background:transparent;border:1.5px solid var(--border-strong);box-shadow:none}
|
||||
.card-accent{background:var(--surface);border-top:3px solid var(--accent)}
|
||||
.card-hover{transition:transform .3s var(--ease),box-shadow .3s var(--ease)}
|
||||
.card-hover:hover{transform:translateY(-4px);box-shadow:var(--shadow-lg)}
|
||||
|
||||
.pill{display:inline-block;padding:4px 12px;border-radius:999px;font-size:12px;font-weight:500;
|
||||
background:var(--surface-2);color:var(--text-2);border:1px solid var(--border)}
|
||||
.pill-accent{background:color-mix(in srgb,var(--accent) 12%,transparent);color:var(--accent);border-color:color-mix(in srgb,var(--accent) 28%,transparent)}
|
||||
|
||||
/* ================= BARS / DIVIDERS ================= */
|
||||
.divider{height:1px;background:var(--border);width:100%}
|
||||
.divider-accent{height:3px;width:72px;background:var(--accent);border-radius:2px}
|
||||
|
||||
/* ================= CHROME (header/footer/progress) ================= */
|
||||
.deck-header{position:absolute;top:24px;left:40px;right:40px;display:flex;align-items:center;justify-content:space-between;
|
||||
font-size:12px;color:var(--text-3);letter-spacing:.12em;text-transform:uppercase;z-index:10;pointer-events:none}
|
||||
.deck-footer{position:absolute;bottom:24px;left:40px;right:40px;display:flex;align-items:center;justify-content:space-between;
|
||||
font-size:12px;color:var(--text-3);z-index:10;pointer-events:none}
|
||||
.slide-number::before{content:attr(data-current)}
|
||||
.slide-number::after{content:" / " attr(data-total)}
|
||||
.progress-bar{position:fixed;left:0;right:0;bottom:0;height:3px;background:transparent;z-index:20}
|
||||
.progress-bar > span{display:block;height:100%;width:0;background:var(--accent);transition:width .3s var(--ease)}
|
||||
|
||||
/* ================= PRESENTER / OVERVIEW ================= */
|
||||
.notes{display:none!important}
|
||||
.notes-overlay{position:fixed;inset:auto 0 0 0;max-height:42vh;background:rgba(20,22,30,.95);color:#e8ebf4;
|
||||
padding:20px 32px;font-size:16px;line-height:1.6;border-top:1px solid rgba(255,255,255,.1);transform:translateY(100%);
|
||||
transition:transform .3s var(--ease);z-index:40;overflow:auto;font-family:var(--font-sans)}
|
||||
.notes-overlay.open{transform:translateY(0)}
|
||||
.overview{position:fixed;inset:0;background:rgba(10,12,18,.92);backdrop-filter:blur(12px);z-index:50;
|
||||
display:none;padding:40px;overflow:auto}
|
||||
.overview.open{display:grid;grid-template-columns:repeat(4,1fr);gap:20px;align-content:start}
|
||||
.overview .thumb{background:var(--surface);border:1px solid var(--border);border-radius:12px;
|
||||
aspect-ratio:16/9;overflow:hidden;cursor:pointer;position:relative;color:var(--text-1);padding:16px;
|
||||
font-size:11px;transition:transform .2s var(--ease)}
|
||||
.overview .thumb:hover{transform:scale(1.04)}
|
||||
.overview .thumb .n{position:absolute;top:8px;left:10px;font-weight:700;font-size:14px;color:var(--text-3)}
|
||||
.overview .thumb .t{position:absolute;bottom:10px;left:14px;right:14px;font-weight:600;color:var(--text-1)}
|
||||
|
||||
/* ================= PRESENTER VIEW ================= */
|
||||
/* Presenter view opens in a separate popup window (S key).
|
||||
* All presenter styles are self-contained in the popup HTML generated by runtime.js.
|
||||
* The audience window (this file) is NOT affected — it stays as normal deck view.
|
||||
* Only the .notes class below is needed to hide speaker notes from audience. */
|
||||
|
||||
/* ================= UTILITY ================= */
|
||||
.hidden{display:none!important}
|
||||
.nowrap{white-space:nowrap}
|
||||
.tr{text-align:right}.tc{text-align:center}.tl{text-align:left}
|
||||
.uppercase{text-transform:uppercase;letter-spacing:.12em}
|
||||
|
||||
/* ================= PRINT ================= */
|
||||
@media print{
|
||||
.slide{position:relative;opacity:1!important;transform:none!important;page-break-after:always;height:100vh}
|
||||
.deck-header,.deck-footer,.progress-bar,.notes-overlay,.overview{display:none!important}
|
||||
}
|
||||
|
||||
</style>
|
||||
<style>/* html-ppt :: animations.css
|
||||
* Apply by adding class="anim-<name>" or data-anim="<name>".
|
||||
* Durations are deliberately snappy; tweak --anim-dur per element.
|
||||
*/
|
||||
:root{--anim-dur:.7s;--anim-ease:cubic-bezier(.4,0,.2,1)}
|
||||
|
||||
/* ---------- FADE DIRECTIONALS ---------- */
|
||||
@keyframes kf-fade-up{from{opacity:0;transform:translateY(32px)}to{opacity:1;transform:none}}
|
||||
@keyframes kf-fade-down{from{opacity:0;transform:translateY(-32px)}to{opacity:1;transform:none}}
|
||||
@keyframes kf-fade-left{from{opacity:0;transform:translateX(-40px)}to{opacity:1;transform:none}}
|
||||
@keyframes kf-fade-right{from{opacity:0;transform:translateX(40px)}to{opacity:1;transform:none}}
|
||||
.anim-fade-up{animation:kf-fade-up var(--anim-dur) var(--anim-ease) both}
|
||||
.anim-fade-down{animation:kf-fade-down var(--anim-dur) var(--anim-ease) both}
|
||||
.anim-fade-left{animation:kf-fade-left var(--anim-dur) var(--anim-ease) both}
|
||||
.anim-fade-right{animation:kf-fade-right var(--anim-dur) var(--anim-ease) both}
|
||||
|
||||
/* ---------- RISE / DROP / ZOOM / BLUR / GLITCH ---------- */
|
||||
@keyframes kf-rise{from{opacity:0;transform:translateY(60px) scale(.97);filter:blur(6px)}to{opacity:1;transform:none;filter:none}}
|
||||
@keyframes kf-drop{from{opacity:0;transform:translateY(-60px) scale(.97)}to{opacity:1;transform:none}}
|
||||
@keyframes kf-zoom{0%{opacity:0;transform:scale(.6)}60%{transform:scale(1.04)}100%{opacity:1;transform:scale(1)}}
|
||||
@keyframes kf-blur{from{opacity:0;filter:blur(18px)}to{opacity:1;filter:none}}
|
||||
@keyframes kf-glitch{0%{opacity:0;transform:translateX(0);clip-path:inset(0 0 0 0)}
|
||||
20%{opacity:1;transform:translateX(-6px);clip-path:inset(20% 0 30% 0)}
|
||||
40%{transform:translateX(4px);clip-path:inset(50% 0 10% 0)}
|
||||
60%{transform:translateX(-3px);clip-path:inset(10% 0 60% 0)}
|
||||
80%{transform:translateX(2px);clip-path:inset(0 0 0 0)}
|
||||
100%{opacity:1;transform:none}}
|
||||
.anim-rise-in{animation:kf-rise .9s var(--anim-ease) both}
|
||||
.anim-drop-in{animation:kf-drop .8s var(--anim-ease) both}
|
||||
.anim-zoom-pop{animation:kf-zoom .7s cubic-bezier(.22,1.3,.36,1) both}
|
||||
.anim-blur-in{animation:kf-blur .8s var(--anim-ease) both}
|
||||
.anim-glitch-in{animation:kf-glitch .8s steps(5,end) both}
|
||||
|
||||
/* ---------- TYPEWRITER ---------- */
|
||||
.anim-typewriter{display:inline-block;overflow:hidden;white-space:nowrap;border-right:2px solid currentColor;
|
||||
width:0;animation:kf-type 2.4s steps(40,end) forwards, kf-caret 1s step-end infinite}
|
||||
@keyframes kf-type{to{width:100%}}
|
||||
@keyframes kf-caret{50%{border-color:transparent}}
|
||||
|
||||
/* ---------- GLOW / SHIMMER / GRADIENT-FLOW ---------- */
|
||||
@keyframes kf-neon{0%,100%{text-shadow:0 0 8px var(--accent),0 0 20px var(--accent)}
|
||||
50%{text-shadow:0 0 16px var(--accent),0 0 40px var(--accent),0 0 80px var(--accent)}}
|
||||
.anim-neon-glow{animation:kf-neon 2s ease-in-out infinite}
|
||||
|
||||
.anim-shimmer-sweep{position:relative;overflow:hidden}
|
||||
.anim-shimmer-sweep::after{content:"";position:absolute;inset:0;
|
||||
background:linear-gradient(110deg,transparent 40%,rgba(255,255,255,.55) 50%,transparent 60%);
|
||||
transform:translateX(-100%);animation:kf-shimmer 2.4s var(--anim-ease) infinite}
|
||||
@keyframes kf-shimmer{to{transform:translateX(100%)}}
|
||||
|
||||
.anim-gradient-flow{background:linear-gradient(90deg,var(--accent),var(--accent-2,var(--accent)),var(--accent-3,var(--accent)),var(--accent));
|
||||
background-size:300% 100%;-webkit-background-clip:text;background-clip:text;color:transparent;-webkit-text-fill-color:transparent;
|
||||
animation:kf-gradflow 4s linear infinite}
|
||||
@keyframes kf-gradflow{to{background-position:300% 0}}
|
||||
|
||||
/* ---------- STAGGER LIST ---------- */
|
||||
.anim-stagger-list > *{opacity:0;animation:kf-rise .65s var(--anim-ease) both}
|
||||
.anim-stagger-list > *:nth-child(1){animation-delay:.05s}
|
||||
.anim-stagger-list > *:nth-child(2){animation-delay:.15s}
|
||||
.anim-stagger-list > *:nth-child(3){animation-delay:.25s}
|
||||
.anim-stagger-list > *:nth-child(4){animation-delay:.35s}
|
||||
.anim-stagger-list > *:nth-child(5){animation-delay:.45s}
|
||||
.anim-stagger-list > *:nth-child(6){animation-delay:.55s}
|
||||
.anim-stagger-list > *:nth-child(7){animation-delay:.65s}
|
||||
.anim-stagger-list > *:nth-child(8){animation-delay:.75s}
|
||||
.anim-stagger-list > *:nth-child(n+9){animation-delay:.85s}
|
||||
|
||||
/* ---------- COUNTER-UP (JS-driven, marker class only) ---------- */
|
||||
.counter{font-variant-numeric:tabular-nums}
|
||||
|
||||
/* ---------- SVG PATH DRAW ---------- */
|
||||
.anim-path-draw path,.anim-path-draw line,.anim-path-draw polyline,.anim-path-draw circle,.anim-path-draw rect{
|
||||
stroke-dasharray:1000;stroke-dashoffset:1000;animation:kf-draw 2s var(--anim-ease) forwards}
|
||||
@keyframes kf-draw{to{stroke-dashoffset:0}}
|
||||
|
||||
/* ---------- PARALLAX TILT (hover) ---------- */
|
||||
.anim-parallax-tilt{transform-style:preserve-3d;transition:transform .4s var(--anim-ease)}
|
||||
.anim-parallax-tilt:hover{transform:perspective(900px) rotateX(6deg) rotateY(-8deg) translateZ(10px)}
|
||||
|
||||
/* ---------- CARD FLIP 3D ---------- */
|
||||
@keyframes kf-flip{from{transform:perspective(1200px) rotateY(-90deg);opacity:0}
|
||||
to{transform:perspective(1200px) rotateY(0);opacity:1}}
|
||||
.anim-card-flip-3d{animation:kf-flip .9s var(--anim-ease) both;transform-style:preserve-3d;backface-visibility:hidden}
|
||||
|
||||
/* ---------- CUBE ROTATE 3D ---------- */
|
||||
@keyframes kf-cube{from{transform:perspective(1200px) rotateX(20deg) rotateY(-90deg) translateZ(-200px);opacity:0}
|
||||
to{transform:perspective(1200px) rotateX(0) rotateY(0) translateZ(0);opacity:1}}
|
||||
.anim-cube-rotate-3d{animation:kf-cube 1s var(--anim-ease) both}
|
||||
|
||||
/* ---------- PAGE TURN 3D ---------- */
|
||||
@keyframes kf-pageturn{from{transform:perspective(1600px) rotateY(-85deg);transform-origin:left center;opacity:0}
|
||||
to{transform:perspective(1600px) rotateY(0);opacity:1}}
|
||||
.anim-page-turn-3d{animation:kf-pageturn 1s var(--anim-ease) both;transform-origin:left center}
|
||||
|
||||
/* ---------- PERSPECTIVE ZOOM ---------- */
|
||||
@keyframes kf-pzoom{from{opacity:0;transform:perspective(1400px) translateZ(-400px) rotateX(12deg)}
|
||||
to{opacity:1;transform:none}}
|
||||
.anim-perspective-zoom{animation:kf-pzoom 1s var(--anim-ease) both}
|
||||
|
||||
/* ---------- MARQUEE SCROLL ---------- */
|
||||
.anim-marquee-scroll{display:flex;gap:48px;white-space:nowrap;animation:kf-marquee 20s linear infinite}
|
||||
@keyframes kf-marquee{from{transform:translateX(0)}to{transform:translateX(-50%)}}
|
||||
|
||||
/* ---------- KEN BURNS ---------- */
|
||||
@keyframes kf-kenburns{0%{transform:scale(1) translate(0,0)}100%{transform:scale(1.15) translate(-2%,-1%)}}
|
||||
.anim-kenburns{animation:kf-kenburns 14s ease-in-out infinite alternate}
|
||||
|
||||
/* ---------- CONFETTI BURST (pseudo — pure CSS sparkles) ---------- */
|
||||
.anim-confetti-burst{position:relative}
|
||||
.anim-confetti-burst::before,.anim-confetti-burst::after{
|
||||
content:"";position:absolute;top:50%;left:50%;width:8px;height:8px;border-radius:50%;
|
||||
background:var(--accent);box-shadow:
|
||||
20px -30px 0 var(--accent-2,var(--accent)),-25px -20px 0 var(--accent-3,var(--accent)),
|
||||
30px 20px 0 var(--good,#1aaf6c),-30px 25px 0 var(--warn,#f5a524),
|
||||
40px -10px 0 var(--bad,#e0445a),-45px 0 0 var(--accent),
|
||||
10px 40px 0 var(--accent-2,var(--accent)),-15px -40px 0 var(--accent-3,var(--accent));
|
||||
opacity:0;animation:kf-confetti 1.2s var(--anim-ease) forwards}
|
||||
.anim-confetti-burst::after{animation-delay:.15s;transform:rotate(45deg)}
|
||||
@keyframes kf-confetti{0%{opacity:0;transform:scale(.2)}30%{opacity:1}100%{opacity:0;transform:scale(2.2)}}
|
||||
|
||||
/* ---------- SPOTLIGHT ---------- */
|
||||
@keyframes kf-spot{0%{clip-path:circle(0% at 50% 50%)}100%{clip-path:circle(140% at 50% 50%)}}
|
||||
.anim-spotlight{animation:kf-spot 1.1s var(--anim-ease) both}
|
||||
|
||||
/* ---------- MORPH SHAPE (SVG) ---------- */
|
||||
.anim-morph-shape path{animation:kf-morph 6s ease-in-out infinite alternate}
|
||||
@keyframes kf-morph{0%{d:path("M60,120 Q120,20 180,120 T300,120")}
|
||||
100%{d:path("M60,120 Q120,220 180,120 T300,120")}}
|
||||
|
||||
/* ---------- RIPPLE REVEAL ---------- */
|
||||
@keyframes kf-ripple{0%{clip-path:circle(0% at 20% 80%);opacity:.4}
|
||||
100%{clip-path:circle(160% at 20% 80%);opacity:1}}
|
||||
.anim-ripple-reveal{animation:kf-ripple 1.2s var(--anim-ease) both}
|
||||
|
||||
/* reduced motion */
|
||||
@media (prefers-reduced-motion: reduce){
|
||||
[class*="anim-"]{animation:none!important;transition:none!important}
|
||||
}
|
||||
|
||||
</style>
|
||||
<style>/* xhs-post — 小红书 3:4 九宫格 */
|
||||
.tpl-xhs-post{
|
||||
--bg:#fef7f3;--bg-soft:#fff1ea;--surface:#ffffff;--surface-2:#fff5ef;
|
||||
--border:rgba(90,40,30,.12);--border-strong:rgba(90,40,30,.24);
|
||||
--text-1:#3a1f18;--text-2:#6f4a3e;--text-3:#a68676;
|
||||
--accent:#ff6b8b;--accent-2:#ffa94d;--accent-3:#ffd166;
|
||||
--grad:linear-gradient(135deg,#ffd3e0,#ffe5c7 50%,#d6f0ff);
|
||||
--good:#7bc67b;--warn:#ffb547;--bad:#ff6b6b;
|
||||
--radius:24px;--radius-lg:32px;
|
||||
--shadow:0 14px 36px rgba(90,40,30,.08);
|
||||
font-family:'Inter','Noto Sans SC','PingFang SC',sans-serif;
|
||||
}
|
||||
.tpl-xhs-post{background:#f0eae2;display:flex;align-items:center;justify-content:center;min-height:100vh}
|
||||
.tpl-xhs-post .deck{width:810px;height:1080px;position:relative;background:transparent}
|
||||
.tpl-xhs-post .slide{
|
||||
position:absolute;inset:0;width:810px;height:1080px;aspect-ratio:3/4;
|
||||
padding:70px 64px;border-radius:28px;overflow:hidden;
|
||||
background:var(--bg);
|
||||
}
|
||||
.tpl-xhs-post .slide::before{content:"";position:absolute;inset:0;background:
|
||||
radial-gradient(45% 30% at 80% 10%,rgba(255,209,102,.35),transparent 70%),
|
||||
radial-gradient(50% 35% at 10% 95%,rgba(255,107,139,.22),transparent 70%),
|
||||
radial-gradient(40% 30% at 90% 85%,rgba(122,200,255,.18),transparent 70%);
|
||||
pointer-events:none;z-index:0}
|
||||
.tpl-xhs-post .slide > *{position:relative;z-index:1}
|
||||
.tpl-xhs-post .h1{font-size:72px;line-height:1.1;font-weight:900;letter-spacing:-.02em;color:var(--text-1)}
|
||||
.tpl-xhs-post .h2{font-size:54px;line-height:1.15;font-weight:800;letter-spacing:-.015em;color:var(--text-1)}
|
||||
.tpl-xhs-post .h3{font-size:36px;font-weight:800;color:var(--text-1)}
|
||||
.tpl-xhs-post .page-dot{position:absolute;top:40px;right:48px;background:var(--text-1);color:#fff;border-radius:999px;padding:6px 14px;font-family:'JetBrains Mono',monospace;font-size:14px;font-weight:700;z-index:2}
|
||||
.tpl-xhs-post .sticker{position:absolute;padding:10px 18px;background:#fff;border:2.5px dashed var(--text-1);border-radius:18px;font-weight:800;font-size:18px;color:var(--text-1);transform:rotate(-3deg);box-shadow:4px 4px 0 var(--text-1)}
|
||||
.tpl-xhs-post .sticker.pink{background:#ffd3e0}
|
||||
.tpl-xhs-post .sticker.yellow{background:#ffe788}
|
||||
.tpl-xhs-post .sticker.blue{background:#cfeaff}
|
||||
.tpl-xhs-post .sticker.green{background:#d4f2c8}
|
||||
.tpl-xhs-post .hand-box{background:#fff;border:2.5px solid var(--text-1);border-radius:22px;padding:24px 28px;box-shadow:5px 5px 0 var(--text-1)}
|
||||
.tpl-xhs-post .lede{color:var(--text-2);font-size:26px;line-height:1.55}
|
||||
.tpl-xhs-post .big-emoji{font-size:180px;line-height:1;text-align:center}
|
||||
.tpl-xhs-post .num-circle{display:inline-flex;align-items:center;justify-content:center;width:72px;height:72px;border-radius:50%;background:var(--accent);color:#fff;font-weight:900;font-size:36px;border:3px solid var(--text-1);box-shadow:4px 4px 0 var(--text-1)}
|
||||
.tpl-xhs-post .step-card{background:#fff;border:2.5px solid var(--text-1);border-radius:22px;padding:26px 28px;box-shadow:5px 5px 0 var(--text-1);margin-bottom:24px}
|
||||
.tpl-xhs-post .step-card h4{font-size:28px;font-weight:800;margin:0 0 6px}
|
||||
.tpl-xhs-post .step-card p{font-size:18px;color:var(--text-2);margin:0}
|
||||
.tpl-xhs-post .tag-row{display:flex;flex-wrap:wrap;gap:10px;margin-top:24px}
|
||||
.tpl-xhs-post .ht{background:#fff;color:var(--accent);border:2px solid var(--text-1);padding:6px 14px;border-radius:999px;font-weight:700;font-size:16px}
|
||||
.tpl-xhs-post .cover-title{background:linear-gradient(180deg,transparent 60%,var(--accent-3) 60%,var(--accent-3) 92%,transparent 92%);padding:0 10px}
|
||||
.tpl-xhs-post .heart{color:var(--accent);font-size:28px}
|
||||
.tpl-xhs-post .bottom-bar{position:absolute;bottom:40px;left:64px;right:64px;display:flex;justify-content:space-between;align-items:center;font-size:15px;color:var(--text-3);font-family:'JetBrains Mono',monospace;z-index:2}
|
||||
.tpl-xhs-post .avatar{width:54px;height:54px;border-radius:50%;background:var(--grad);border:2.5px solid var(--text-1);box-shadow:3px 3px 0 var(--text-1);display:inline-flex;align-items:center;justify-content:center;font-weight:900;font-size:20px;color:var(--text-1)}
|
||||
|
||||
</style>
|
||||
<style>
|
||||
/* Static-preview fallback (runtime.js is absent — keep every slide visible) */
|
||||
.deck{height:auto;min-height:100vh;overflow:visible}
|
||||
.slide{position:relative;inset:auto;opacity:1;pointer-events:auto;transform:none;height:100vh;page-break-after:always}
|
||||
.deck-header,.deck-footer,.slide-number,.progress-bar,.notes-overlay,.overview{pointer-events:none}
|
||||
.notes{display:none!important}
|
||||
</style></head>
|
||||
<body class="tpl-xhs-post">
|
||||
<div class="deck">
|
||||
|
||||
<!-- 1. Cover -->
|
||||
<section class="slide" data-title="Cover">
|
||||
<div class="page-dot">1 / 9</div>
|
||||
<div class="sticker pink" style="top:120px;left:48px;transform:rotate(-6deg)">💤 救命</div>
|
||||
<div class="sticker yellow" style="top:140px;right:64px;transform:rotate(5deg)">亲测 7 天</div>
|
||||
<div style="margin-top:200px">
|
||||
<p class="lede" style="font-size:24px;color:var(--text-1);font-weight:600">打工人深夜自救手册</p>
|
||||
<h1 class="h1 mt-s">每天只睡 <span class="cover-title">6h</span><br>还能<span class="cover-title">精神一整天</span><br>的 3 个小习惯</h1>
|
||||
</div>
|
||||
<div class="bottom-bar"><div><span class="avatar">小</span> <b style="color:var(--text-1);margin-left:8px">@小熊不困了</b></div><div>← 左滑 查看</div></div>
|
||||
</section>
|
||||
|
||||
<!-- 2. Hook -->
|
||||
<section class="slide" data-title="Hook">
|
||||
<div class="page-dot">2 / 9</div>
|
||||
<div class="big-emoji" style="margin-top:80px">👀</div>
|
||||
<h2 class="h2 tc mt-l">等等先别划走!</h2>
|
||||
<p class="lede tc mt-m" style="padding:0 20px">我也曾是那个<br>早上起来像被卡车撞过的人。<br><br>直到我发现了<br><b style="color:var(--accent)">1 件事</b>比睡够 8 小时还重要。</p>
|
||||
<div class="sticker blue" style="bottom:160px;left:50%;transform:translateX(-50%) rotate(-2deg)">真 · 转折点 ↓</div>
|
||||
</section>
|
||||
|
||||
<!-- 3. Pain -->
|
||||
<section class="slide" data-title="Pain">
|
||||
<div class="page-dot">3 / 9</div>
|
||||
<p class="lede" style="font-weight:700;color:var(--accent)">❌ 你是不是也这样</p>
|
||||
<h2 class="h2 mt-s">越睡越累</h2>
|
||||
<div class="stack mt-l">
|
||||
<div class="hand-box"><b style="font-size:22px">😵💫 周末补觉到中午</b><p class="dim" style="font-size:16px;margin-top:4px">起来头更晕,一整天废掉</p></div>
|
||||
<div class="hand-box"><b style="font-size:22px">☕️ 咖啡续三杯</b><p class="dim" style="font-size:16px;margin-top:4px">下午 3 点照样困到扶墙</p></div>
|
||||
<div class="hand-box"><b style="font-size:22px">📱 睡前刷到凌晨</b><p class="dim" style="font-size:16px;margin-top:4px">明明很困就是不舍得睡</p></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 4. Aha -->
|
||||
<section class="slide" data-title="Aha">
|
||||
<div class="page-dot">4 / 9</div>
|
||||
<div class="sticker green" style="top:100px;right:48px;transform:rotate(4deg)">✨ aha moment</div>
|
||||
<p class="lede mt-l" style="color:var(--accent);font-weight:700">💡 真相是</p>
|
||||
<h2 class="h2 mt-s">不是睡得少,<br>是<span style="background:var(--accent-3);padding:0 8px">醒得不对</span>。</h2>
|
||||
<p class="lede mt-l">身体有 90 分钟一个周期。<br>在"深睡"里被闹钟拽起来,<br>就算睡 9 小时也跟没睡一样。</p>
|
||||
<p class="lede mt-m" style="color:var(--text-1);font-weight:700">关键是:<span style="color:var(--accent)">卡着周期醒</span>。</p>
|
||||
</section>
|
||||
|
||||
<!-- 5. Step 1 -->
|
||||
<section class="slide" data-title="Step 1">
|
||||
<div class="page-dot">5 / 9</div>
|
||||
<div class="num-circle">1</div>
|
||||
<h2 class="h2 mt-m">倒推睡眠时间</h2>
|
||||
<div class="hand-box mt-l">
|
||||
<p style="font-size:22px;margin:0;color:var(--text-1);font-weight:700">👉 公式</p>
|
||||
<p style="font-size:20px;margin:10px 0 0;color:var(--text-2);line-height:1.7">起床时间 − <b style="color:var(--accent)">90min × N</b> − 15min 入睡<br>= 你今晚该上床的点</p>
|
||||
</div>
|
||||
<div class="hand-box mt-m" style="background:#fff5ef">
|
||||
<p style="font-size:18px;margin:0;color:var(--text-2)">举例:要 7 点起</p>
|
||||
<p style="font-size:24px;margin:8px 0 0;color:var(--text-1);font-weight:800">→ 23:15 上床 (4 个周期)<br>→ 00:45 上床 (3 个周期)</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 6. Step 2 -->
|
||||
<section class="slide" data-title="Step 2">
|
||||
<div class="page-dot">6 / 9</div>
|
||||
<div class="num-circle" style="background:var(--accent-2)">2</div>
|
||||
<h2 class="h2 mt-m">早晨 10 分钟光</h2>
|
||||
<div class="hand-box mt-l">
|
||||
<p style="font-size:22px;margin:0;color:var(--text-1);font-weight:700">☀️ 打开窗帘 / 下楼遛弯</p>
|
||||
<p style="font-size:18px;margin:8px 0 0;color:var(--text-2);line-height:1.6">自然光一照,褪黑素立刻被掐停,人就真的醒了。阴天也有效,别偷懒。</p>
|
||||
</div>
|
||||
<div class="sticker yellow" style="bottom:200px;right:60px;transform:rotate(8deg)">⏰ 比咖啡还猛</div>
|
||||
<div class="hand-box mt-m" style="background:#fff5ef">
|
||||
<p style="font-size:18px;margin:0;color:var(--text-2)">懒人方案:</p>
|
||||
<p style="font-size:22px;margin:6px 0 0;color:var(--text-1);font-weight:700">刷牙的时候站在窗边 🪥</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 7. Step 3 -->
|
||||
<section class="slide" data-title="Step 3">
|
||||
<div class="page-dot">7 / 9</div>
|
||||
<div class="num-circle" style="background:var(--accent-3);color:var(--text-1)">3</div>
|
||||
<h2 class="h2 mt-m">下午 3 点<br>20 分钟小睡</h2>
|
||||
<div class="hand-box mt-l">
|
||||
<p style="font-size:20px;margin:0;color:var(--text-2);line-height:1.6"><b style="color:var(--text-1)">⏱️ 最多 20 分钟。</b>超过 30 就会进入深睡,醒来会更累。</p>
|
||||
</div>
|
||||
<div class="hand-box mt-m" style="background:#fff5ef">
|
||||
<p style="font-size:20px;margin:0;color:var(--text-2);line-height:1.6"><b style="color:var(--text-1)">💡 小 tip:</b>睡前喝一口咖啡。20 分钟后咖啡因正好起效,和小睡的清醒 buff 叠加。</p>
|
||||
</div>
|
||||
<div class="sticker pink" style="bottom:140px;left:50%;transform:translateX(-50%) rotate(-3deg)">打工人作弊技</div>
|
||||
</section>
|
||||
|
||||
<!-- 8. Result -->
|
||||
<section class="slide" data-title="Result">
|
||||
<div class="page-dot">8 / 9</div>
|
||||
<p class="lede" style="color:var(--good);font-weight:700">✅ 我坚持 7 天后</p>
|
||||
<h2 class="h2 mt-s">结果是……</h2>
|
||||
<div class="stack mt-l">
|
||||
<div class="hand-box"><b style="font-size:22px">😌 早上闹钟响之前就自然醒</b></div>
|
||||
<div class="hand-box"><b style="font-size:22px">💪 下午不再崩溃</b></div>
|
||||
<div class="hand-box"><b style="font-size:22px">☕️ 咖啡从 3 杯 → 1 杯</b></div>
|
||||
<div class="hand-box" style="background:var(--accent-3);border-color:var(--text-1)"><b style="font-size:24px">✨ 最重要:脾气变好了</b></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 9. CTA -->
|
||||
<section class="slide" data-title="CTA">
|
||||
<div class="page-dot">9 / 9</div>
|
||||
<div class="big-emoji" style="margin-top:60px">💌</div>
|
||||
<h2 class="h2 tc mt-l">觉得有用的话</h2>
|
||||
<h1 class="h1 tc mt-s" style="color:var(--accent)">收藏 + 关注 🧡</h1>
|
||||
<p class="lede tc mt-l" style="padding:0 30px">下期讲<br><b style="color:var(--text-1)">「打工人脊椎急救 5 式」</b><br>办公室也能做</p>
|
||||
<div class="tag-row" style="justify-content:center;margin-top:36px">
|
||||
<span class="ht">#睡眠</span>
|
||||
<span class="ht">#打工人日常</span>
|
||||
<span class="ht">#自律</span>
|
||||
<span class="ht">#健康生活</span>
|
||||
</div>
|
||||
<div class="bottom-bar"><div><span class="avatar">小</span> <b style="color:var(--text-1);margin-left:8px">@小熊不困了</b></div><div>❤️ 5.2w</div></div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
</body></html>
|
||||
78
skills/html-ppt-xhs-white-editorial/SKILL.md
Normal file
78
skills/html-ppt-xhs-white-editorial/SKILL.md
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
---
|
||||
name: html-ppt-xhs-white-editorial
|
||||
description: 白底杂志风 deck — 纯白背景 + 顶部 10 色彩虹 bar、80-110px display 标题、紫→蓝→绿→橙→粉渐变文字、马卡龙软卡片组(粉/紫/蓝/绿/橙)、黑底白字 .focus pill、引用大块。同时适合发小红书图文 + 横版 PPT 双用。
|
||||
triggers:
|
||||
- "白底杂志"
|
||||
- "杂志风"
|
||||
- "xhs editorial"
|
||||
- "white editorial"
|
||||
- "小红书白底"
|
||||
- "editorial deck"
|
||||
od:
|
||||
mode: deck
|
||||
scenario: marketing
|
||||
featured: 27
|
||||
upstream: "https://github.com/lewislulu/html-ppt-skill"
|
||||
preview:
|
||||
type: html
|
||||
entry: index.html
|
||||
design_system:
|
||||
requires: false
|
||||
speaker_notes: true
|
||||
animations: true
|
||||
example_prompt: "用 html-ppt-xhs-white-editorial 模板做一份白底杂志风 PPT,中文优先。要点:80-110px display 大标题、彩虹顶部 bar、马卡龙软卡片、黑底白字 .focus pill。先告诉我主题和受众,再写 8-12 页。"
|
||||
---
|
||||
# HTML PPT · 白底杂志风
|
||||
|
||||
A focused entry point into the [`html-ppt`](../html-ppt/SKILL.md) master skill that lands the user directly on the **`xhs-white-editorial`** full-deck template.
|
||||
|
||||
## When this card is picked
|
||||
|
||||
The Examples gallery wires "Use this prompt" to the example_prompt above. When you accept that prompt, this card is the right pick if the user wants exactly the visual identity of `xhs-white-editorial` (see the upstream [full-decks catalog](../html-ppt/references/full-decks.md) for screenshots and rationale).
|
||||
|
||||
## How to author the deck
|
||||
|
||||
1. **Read the master skill first.** All authoring rules live in
|
||||
[`skills/html-ppt/SKILL.md`](../html-ppt/SKILL.md) — content/audience checklist,
|
||||
token rules, layout reuse, presenter mode, the keyboard runtime, and the
|
||||
"never put presenter-only text on the slide" rule.
|
||||
2. **Start from the matching template folder:**
|
||||
`skills/html-ppt/templates/full-decks/xhs-white-editorial/` — copy `index.html` and
|
||||
`style.css` into the project, keep the `.tpl-xhs-white-editorial` body class.
|
||||
3. **Bring the shared runtime with the template.** The upstream
|
||||
`index.html` links the shared CSS/JS via `../../../assets/...` because it
|
||||
sits three folders deep inside `skills/html-ppt/templates/full-decks/`.
|
||||
Once you copy `index.html` into the project, those parent-relative URLs
|
||||
no longer resolve and `base.css`, `animations.css`, and `runtime.js`
|
||||
will 404 — meaning the deck never activates and slide navigation is
|
||||
dead. Pick one of these two recipes per project:
|
||||
- **Recipe A — copy + rewrite (preferred):** copy
|
||||
`skills/html-ppt/assets/fonts.css`, `skills/html-ppt/assets/base.css`,
|
||||
`skills/html-ppt/assets/animations/animations.css`, and
|
||||
`skills/html-ppt/assets/runtime.js` into a project-local
|
||||
`assets/` (with `assets/animations/animations.css`), then rewrite the
|
||||
four `<link>`/`<script>` tags in `index.html` from
|
||||
`../../../assets/...` to the matching project-local paths
|
||||
(`assets/fonts.css`, `assets/base.css`,
|
||||
`assets/animations/animations.css`, `assets/runtime.js`).
|
||||
- **Recipe B — inline:** read the same four files and replace each
|
||||
`<link rel="stylesheet" href="../../../assets/...">` with a
|
||||
`<style>...</style>` containing the file's contents, and the
|
||||
`<script src="../../../assets/runtime.js">` with a
|
||||
`<script>...</script>` containing `runtime.js`. Yields a single
|
||||
self-contained `index.html`.
|
||||
Either way, do not ship the upstream `../../../assets/...` URLs
|
||||
verbatim into a project artifact — they only work in-tree.
|
||||
4. **Pick a theme.** Default tokens look fine; if the user wants a different
|
||||
feel, swap in any of the 36 themes from `skills/html-ppt/assets/themes/*.css`
|
||||
via `<link id="theme-link">` and let `T` cycle.
|
||||
5. **Replace demo content, not classes.** The `.tpl-xhs-white-editorial` scoped CSS only
|
||||
recognises the structural classes shipped in the template — keep them.
|
||||
6. **Speaker notes go inside `<aside class="notes">` or `<div class="notes">`** — never as visible text on the slide.
|
||||
|
||||
## Attribution
|
||||
|
||||
Visual system, layouts, themes and the runtime keyboard model come from
|
||||
the upstream MIT-licensed [`lewislulu/html-ppt-skill`](https://github.com/lewislulu/html-ppt-skill). The
|
||||
LICENSE file ships at `skills/html-ppt/LICENSE`; please keep it in place when
|
||||
redistributing.
|
||||
418
skills/html-ppt-xhs-white-editorial/example.html
Normal file
418
skills/html-ppt-xhs-white-editorial/example.html
Normal file
|
|
@ -0,0 +1,418 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>白底杂志风 · XHS Editorial</title>
|
||||
<style>/* html-ppt :: shared webfonts */
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@200;300;400;500;600;700;800;900&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@200;300;400;500;600;700;900&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@300;400;600;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,600;0,800;1,400&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@300;400;500;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Archivo+Black&display=swap');
|
||||
|
||||
</style>
|
||||
<style>/* html-ppt :: base.css — reset + shared tokens + layout primitives */
|
||||
/* Default tokens. Themes in assets/themes/*.css override the :root block. */
|
||||
:root {
|
||||
--bg: #ffffff;
|
||||
--bg-soft: #f7f7f8;
|
||||
--surface: #ffffff;
|
||||
--surface-2: #f2f2f4;
|
||||
--border: rgba(0,0,0,.08);
|
||||
--border-strong: rgba(0,0,0,.16);
|
||||
--text-1: #111216;
|
||||
--text-2: #55596a;
|
||||
--text-3: #8a8f9e;
|
||||
--accent: #3b6cff;
|
||||
--accent-2: #7a5cff;
|
||||
--accent-3: #ff5c8a;
|
||||
--good: #1aaf6c;
|
||||
--warn: #f5a524;
|
||||
--bad: #e0445a;
|
||||
--grad: linear-gradient(135deg,#3b6cff,#7a5cff 55%,#ff5c8a);
|
||||
--grad-soft: linear-gradient(135deg,#eef2ff,#f5ecff 55%,#ffeef5);
|
||||
--radius: 18px;
|
||||
--radius-sm: 12px;
|
||||
--radius-lg: 26px;
|
||||
--shadow: 0 10px 30px rgba(18,24,40,.08), 0 2px 6px rgba(18,24,40,.04);
|
||||
--shadow-lg: 0 24px 60px rgba(18,24,40,.14), 0 6px 16px rgba(18,24,40,.06);
|
||||
--font-sans: 'Inter','Noto Sans SC',-apple-system,BlinkMacSystemFont,Helvetica,Arial,sans-serif;
|
||||
--font-serif: 'Playfair Display','Noto Serif SC',Georgia,serif;
|
||||
--font-mono: 'JetBrains Mono','IBM Plex Mono',SFMono-Regular,Menlo,monospace;
|
||||
--font-display: var(--font-sans);
|
||||
--letter-tight: -.03em;
|
||||
--letter-normal: -.01em;
|
||||
--ease: cubic-bezier(.4,0,.2,1);
|
||||
}
|
||||
|
||||
*,*::before,*::after{box-sizing:border-box}
|
||||
html,body{margin:0;padding:0;background:var(--bg);color:var(--text-1);
|
||||
font-family:var(--font-sans);font-weight:400;line-height:1.6;
|
||||
-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;
|
||||
letter-spacing:var(--letter-normal)}
|
||||
img,svg,video{max-width:100%;display:block}
|
||||
a{color:var(--accent);text-decoration:none}
|
||||
a:hover{text-decoration:underline}
|
||||
code,kbd,pre,samp{font-family:var(--font-mono)}
|
||||
|
||||
/* ================= SLIDE SYSTEM ================= */
|
||||
.deck{position:relative;width:100vw;height:100vh;overflow:hidden;background:var(--bg)}
|
||||
.slide{
|
||||
position:absolute;inset:0;
|
||||
display:flex;flex-direction:column;justify-content:center;
|
||||
padding:72px 96px;
|
||||
box-sizing:border-box;
|
||||
opacity:0;pointer-events:none;
|
||||
transition:opacity .5s var(--ease), transform .5s var(--ease);
|
||||
transform:translateX(30px);
|
||||
overflow:hidden;
|
||||
}
|
||||
.slide.is-active{opacity:1;pointer-events:auto;transform:translateX(0);z-index:2}
|
||||
.slide.is-prev{transform:translateX(-30px)}
|
||||
|
||||
/* single-page standalone (used when a layout file is opened directly) */
|
||||
body.single .slide{position:relative;width:100vw;height:100vh;opacity:1;transform:none;pointer-events:auto}
|
||||
|
||||
/* ================= TYPOGRAPHY ================= */
|
||||
.eyebrow{font-size:13px;font-weight:500;letter-spacing:.16em;text-transform:uppercase;color:var(--text-3)}
|
||||
.kicker{font-size:14px;font-weight:600;color:var(--accent);letter-spacing:.08em;text-transform:uppercase}
|
||||
h1.title,.h1{font-family:var(--font-display);font-size:72px;line-height:1.05;font-weight:800;letter-spacing:var(--letter-tight);margin:0 0 18px;color:var(--text-1)}
|
||||
h2.title,.h2{font-family:var(--font-display);font-size:54px;line-height:1.1;font-weight:700;letter-spacing:var(--letter-tight);margin:0 0 14px}
|
||||
h3,.h3{font-size:32px;line-height:1.2;font-weight:600;letter-spacing:var(--letter-normal);margin:0 0 10px}
|
||||
h4,.h4{font-size:22px;line-height:1.3;font-weight:600;margin:0 0 8px}
|
||||
.lede{font-size:22px;line-height:1.55;color:var(--text-2);font-weight:300;max-width:62ch}
|
||||
.dim{color:var(--text-2)}
|
||||
.dim2{color:var(--text-3)}
|
||||
.mono{font-family:var(--font-mono)}
|
||||
.serif{font-family:var(--font-serif)}
|
||||
.gradient-text{background:var(--grad);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;color:transparent}
|
||||
|
||||
/* ================= LAYOUT PRIMITIVES ================= */
|
||||
.stack>*+*{margin-top:14px}
|
||||
.row{display:flex;gap:24px;align-items:center}
|
||||
.row.wrap{flex-wrap:wrap}
|
||||
.grid{display:grid;gap:24px}
|
||||
.g2{grid-template-columns:repeat(2,1fr)}
|
||||
.g3{grid-template-columns:repeat(3,1fr)}
|
||||
.g4{grid-template-columns:repeat(4,1fr)}
|
||||
.center{display:flex;align-items:center;justify-content:center;text-align:center}
|
||||
.fill{flex:1}
|
||||
.sp-t{padding-top:24px}.sp-b{padding-bottom:24px}
|
||||
.mt-s{margin-top:8px}.mt-m{margin-top:18px}.mt-l{margin-top:32px}
|
||||
.mb-s{margin-bottom:8px}.mb-m{margin-bottom:18px}.mb-l{margin-bottom:32px}
|
||||
|
||||
/* ================= CARDS ================= */
|
||||
.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);
|
||||
padding:26px 28px;box-shadow:var(--shadow);position:relative;overflow:hidden}
|
||||
.card-soft{background:var(--surface-2);border:1px solid var(--border)}
|
||||
.card-outline{background:transparent;border:1.5px solid var(--border-strong);box-shadow:none}
|
||||
.card-accent{background:var(--surface);border-top:3px solid var(--accent)}
|
||||
.card-hover{transition:transform .3s var(--ease),box-shadow .3s var(--ease)}
|
||||
.card-hover:hover{transform:translateY(-4px);box-shadow:var(--shadow-lg)}
|
||||
|
||||
.pill{display:inline-block;padding:4px 12px;border-radius:999px;font-size:12px;font-weight:500;
|
||||
background:var(--surface-2);color:var(--text-2);border:1px solid var(--border)}
|
||||
.pill-accent{background:color-mix(in srgb,var(--accent) 12%,transparent);color:var(--accent);border-color:color-mix(in srgb,var(--accent) 28%,transparent)}
|
||||
|
||||
/* ================= BARS / DIVIDERS ================= */
|
||||
.divider{height:1px;background:var(--border);width:100%}
|
||||
.divider-accent{height:3px;width:72px;background:var(--accent);border-radius:2px}
|
||||
|
||||
/* ================= CHROME (header/footer/progress) ================= */
|
||||
.deck-header{position:absolute;top:24px;left:40px;right:40px;display:flex;align-items:center;justify-content:space-between;
|
||||
font-size:12px;color:var(--text-3);letter-spacing:.12em;text-transform:uppercase;z-index:10;pointer-events:none}
|
||||
.deck-footer{position:absolute;bottom:24px;left:40px;right:40px;display:flex;align-items:center;justify-content:space-between;
|
||||
font-size:12px;color:var(--text-3);z-index:10;pointer-events:none}
|
||||
.slide-number::before{content:attr(data-current)}
|
||||
.slide-number::after{content:" / " attr(data-total)}
|
||||
.progress-bar{position:fixed;left:0;right:0;bottom:0;height:3px;background:transparent;z-index:20}
|
||||
.progress-bar > span{display:block;height:100%;width:0;background:var(--accent);transition:width .3s var(--ease)}
|
||||
|
||||
/* ================= PRESENTER / OVERVIEW ================= */
|
||||
.notes{display:none!important}
|
||||
.notes-overlay{position:fixed;inset:auto 0 0 0;max-height:42vh;background:rgba(20,22,30,.95);color:#e8ebf4;
|
||||
padding:20px 32px;font-size:16px;line-height:1.6;border-top:1px solid rgba(255,255,255,.1);transform:translateY(100%);
|
||||
transition:transform .3s var(--ease);z-index:40;overflow:auto;font-family:var(--font-sans)}
|
||||
.notes-overlay.open{transform:translateY(0)}
|
||||
.overview{position:fixed;inset:0;background:rgba(10,12,18,.92);backdrop-filter:blur(12px);z-index:50;
|
||||
display:none;padding:40px;overflow:auto}
|
||||
.overview.open{display:grid;grid-template-columns:repeat(4,1fr);gap:20px;align-content:start}
|
||||
.overview .thumb{background:var(--surface);border:1px solid var(--border);border-radius:12px;
|
||||
aspect-ratio:16/9;overflow:hidden;cursor:pointer;position:relative;color:var(--text-1);padding:16px;
|
||||
font-size:11px;transition:transform .2s var(--ease)}
|
||||
.overview .thumb:hover{transform:scale(1.04)}
|
||||
.overview .thumb .n{position:absolute;top:8px;left:10px;font-weight:700;font-size:14px;color:var(--text-3)}
|
||||
.overview .thumb .t{position:absolute;bottom:10px;left:14px;right:14px;font-weight:600;color:var(--text-1)}
|
||||
|
||||
/* ================= PRESENTER VIEW ================= */
|
||||
/* Presenter view opens in a separate popup window (S key).
|
||||
* All presenter styles are self-contained in the popup HTML generated by runtime.js.
|
||||
* The audience window (this file) is NOT affected — it stays as normal deck view.
|
||||
* Only the .notes class below is needed to hide speaker notes from audience. */
|
||||
|
||||
/* ================= UTILITY ================= */
|
||||
.hidden{display:none!important}
|
||||
.nowrap{white-space:nowrap}
|
||||
.tr{text-align:right}.tc{text-align:center}.tl{text-align:left}
|
||||
.uppercase{text-transform:uppercase;letter-spacing:.12em}
|
||||
|
||||
/* ================= PRINT ================= */
|
||||
@media print{
|
||||
.slide{position:relative;opacity:1!important;transform:none!important;page-break-after:always;height:100vh}
|
||||
.deck-header,.deck-footer,.progress-bar,.notes-overlay,.overview{display:none!important}
|
||||
}
|
||||
|
||||
</style>
|
||||
<style>/* xhs-white-editorial — 白底杂志风 */
|
||||
.tpl-xhs-white-editorial{
|
||||
--xw-bg:#ffffff;
|
||||
--xw-ink:#111318;
|
||||
--xw-ink2:#475467;
|
||||
--xw-muted:#98a2b3;
|
||||
--xw-line:#eaecf3;
|
||||
--xw-purple:#7b61ff;
|
||||
--xw-pink:#ff5fa2;
|
||||
--xw-blue:#4e8cff;
|
||||
--xw-green:#17b26a;
|
||||
--xw-orange:#ff9d42;
|
||||
--xw-soft-purple:#f4efff;
|
||||
--xw-soft-pink:#fff0f6;
|
||||
--xw-soft-blue:#eef4ff;
|
||||
--xw-soft-green:#edfdf3;
|
||||
--xw-soft-orange:#fff5ea;
|
||||
background:var(--xw-bg);
|
||||
color:var(--xw-ink);
|
||||
font-family:'Inter','Noto Sans SC','PingFang SC',-apple-system,sans-serif;
|
||||
}
|
||||
.tpl-xhs-white-editorial .slide{background:#fff;padding:72px 88px}
|
||||
.tpl-xhs-white-editorial .xw-topbar{display:flex;justify-content:space-between;align-items:center;margin-bottom:18px}
|
||||
.tpl-xhs-white-editorial .xw-tag{display:inline-flex;align-items:center;gap:10px;padding:10px 18px;border:1px solid var(--xw-line);border-radius:999px;font-size:15px;color:var(--xw-ink2);background:#fff}
|
||||
.tpl-xhs-white-editorial .xw-tag .dot{width:10px;height:10px;border-radius:50%;background:linear-gradient(90deg,#7b61ff,#4e8cff,#17b26a,#ff9d42,#ff5fa2)}
|
||||
.tpl-xhs-white-editorial .xw-page{font-size:14px;color:var(--xw-muted);letter-spacing:.1em}
|
||||
.tpl-xhs-white-editorial .xw-kicker{font-size:18px;color:var(--xw-ink2);margin-top:6px;font-weight:500}
|
||||
.tpl-xhs-white-editorial .xw-title{font-size:84px;line-height:1.02;letter-spacing:-2px;font-weight:850;margin:18px 0 0;color:var(--xw-ink)}
|
||||
.tpl-xhs-white-editorial .xw-title-md{font-size:60px;line-height:1.05;letter-spacing:-1.5px;font-weight:800;margin:14px 0 0}
|
||||
.tpl-xhs-white-editorial .xw-grad{background:linear-gradient(90deg,#7b61ff 0%,#4e8cff 25%,#17b26a 48%,#ff9d42 72%,#ff5fa2 100%);-webkit-background-clip:text;background-clip:text;color:transparent}
|
||||
.tpl-xhs-white-editorial .xw-sub{font-size:24px;line-height:1.45;color:#1f2937;margin-top:22px;max-width:900px}
|
||||
.tpl-xhs-white-editorial .xw-focus{display:inline-block;padding:6px 14px;border-radius:14px;background:#111318;color:#fff;font-weight:700}
|
||||
.tpl-xhs-white-editorial .xw-focus-blue{display:inline-block;padding:6px 14px;border-radius:14px;background:var(--xw-soft-blue);color:#174ea6;font-weight:700}
|
||||
.tpl-xhs-white-editorial .xw-focus-pink{display:inline-block;padding:6px 14px;border-radius:14px;background:var(--xw-soft-pink);color:#c11574;font-weight:700}
|
||||
.tpl-xhs-white-editorial .xw-focus-orange{display:inline-block;padding:6px 14px;border-radius:14px;background:var(--xw-soft-orange);color:#b54708;font-weight:700}
|
||||
.tpl-xhs-white-editorial .xw-focus-green{display:inline-block;padding:6px 14px;border-radius:14px;background:var(--xw-soft-green);color:#067647;font-weight:700}
|
||||
.tpl-xhs-white-editorial .xw-hero{margin-top:28px;border:1px solid var(--xw-line);border-radius:28px;padding:30px 34px;background:linear-gradient(180deg,#fff 0%,#fcfcff 100%);box-shadow:0 18px 48px rgba(17,19,24,.08)}
|
||||
.tpl-xhs-white-editorial .xw-quote{font-size:38px;line-height:1.3;font-weight:800;letter-spacing:-.5px}
|
||||
.tpl-xhs-white-editorial .xw-grid-2{display:grid;grid-template-columns:1fr 1fr;gap:18px;margin-top:22px}
|
||||
.tpl-xhs-white-editorial .xw-grid-3{display:grid;grid-template-columns:1fr 1fr 1fr;gap:16px;margin-top:22px}
|
||||
.tpl-xhs-white-editorial .xw-card{border:1px solid var(--xw-line);border-radius:24px;padding:24px 26px;box-shadow:0 10px 24px rgba(17,19,24,.04);background:#fff}
|
||||
.tpl-xhs-white-editorial .xw-card.soft-purple{background:var(--xw-soft-purple)}
|
||||
.tpl-xhs-white-editorial .xw-card.soft-pink{background:var(--xw-soft-pink)}
|
||||
.tpl-xhs-white-editorial .xw-card.soft-blue{background:var(--xw-soft-blue)}
|
||||
.tpl-xhs-white-editorial .xw-card.soft-green{background:var(--xw-soft-green)}
|
||||
.tpl-xhs-white-editorial .xw-card.soft-orange{background:var(--xw-soft-orange)}
|
||||
.tpl-xhs-white-editorial .xw-label{font-size:14px;font-weight:800;opacity:.7;margin-bottom:10px;letter-spacing:.08em;text-transform:uppercase}
|
||||
.tpl-xhs-white-editorial .xw-card .main{font-size:28px;line-height:1.22;font-weight:850;letter-spacing:-.5px}
|
||||
.tpl-xhs-white-editorial .xw-card .desc{font-size:16px;line-height:1.5;color:#475467;margin-top:12px}
|
||||
.tpl-xhs-white-editorial .xw-steps{margin-top:18px}
|
||||
.tpl-xhs-white-editorial .xw-step{display:flex;gap:18px;align-items:flex-start;margin:16px 0}
|
||||
.tpl-xhs-white-editorial .xw-num{flex:0 0 48px;height:48px;border-radius:50%;background:#111318;color:#fff;display:grid;place-items:center;font-size:20px;font-weight:900}
|
||||
.tpl-xhs-white-editorial .xw-txt{font-size:22px;line-height:1.45;font-weight:700}
|
||||
.tpl-xhs-white-editorial .xw-codebox{background:#0f1117;color:#e4e2d8;border-radius:18px;padding:22px 26px;font-family:'JetBrains Mono',monospace;font-size:15px;line-height:1.75;margin-top:20px;border:1px solid #1f222c}
|
||||
.tpl-xhs-white-editorial .xw-codebox .cm{color:#6b6a62}
|
||||
.tpl-xhs-white-editorial .xw-codebox .kw{color:#c88f64}
|
||||
.tpl-xhs-white-editorial .xw-codebox .st{color:#a8c292}
|
||||
.tpl-xhs-white-editorial .xw-codebox .hl{color:#e9c58a;font-weight:600}
|
||||
.tpl-xhs-white-editorial .xw-footer{position:absolute;left:88px;right:88px;bottom:44px;display:flex;justify-content:space-between;align-items:flex-end;font-size:13px;color:var(--xw-muted)}
|
||||
.tpl-xhs-white-editorial .xw-topline{position:absolute;top:0;left:0;right:0;height:5px;background:linear-gradient(90deg,#6366f1,#8b5cf6,#a855f7,#ec4899,#f43f5e,#f97316,#eab308,#22c55e,#06b6d4,#6366f1)}
|
||||
.tpl-xhs-white-editorial .xw-pill{display:inline-block;padding:8px 16px;border-radius:999px;font-size:14px;font-weight:700;margin:0 8px 8px 0;background:#fff;border:1px solid var(--xw-line);color:#394150}
|
||||
.tpl-xhs-white-editorial .xw-big-stat{font-size:96px;font-weight:900;letter-spacing:-4px;line-height:1}
|
||||
.tpl-xhs-white-editorial .xw-big-stat small{font-size:22px;color:var(--xw-muted);font-weight:700;letter-spacing:0;margin-left:6px}
|
||||
|
||||
</style>
|
||||
<style>
|
||||
/* Static-preview fallback (runtime.js is absent — keep every slide visible) */
|
||||
.deck{height:auto;min-height:100vh;overflow:visible}
|
||||
.slide{position:relative;inset:auto;opacity:1;pointer-events:auto;transform:none;height:100vh;page-break-after:always}
|
||||
.deck-header,.deck-footer,.slide-number,.progress-bar,.notes-overlay,.overview{pointer-events:none}
|
||||
.notes{display:none!important}
|
||||
</style></head>
|
||||
<body class="tpl-xhs-white-editorial">
|
||||
<div class="deck">
|
||||
|
||||
<!-- 1. COVER -->
|
||||
<section class="slide is-active">
|
||||
<div class="xw-topline"></div>
|
||||
<div class="xw-topbar">
|
||||
<div class="xw-tag"><span class="dot"></span>AI 时代 · 职业判断</div>
|
||||
<div class="xw-page">01 / 08</div>
|
||||
</div>
|
||||
<div class="xw-kicker">我越来越确定的一件事</div>
|
||||
<h1 class="xw-title">以后最贵的工作,<br>是 <span class="xw-grad">测试 + 安全</span></h1>
|
||||
<p class="xw-sub">AI 会越来越会做事。但谁来保证它 <span class="xw-focus">做对</span>、<span class="xw-focus">没风险</span>、<span class="xw-focus">不会出事</span>?</p>
|
||||
<div class="xw-hero">
|
||||
<div class="xw-quote">未来最值钱的,<br>不是 <span class="xw-focus-orange">生产</span>,而是 <span class="xw-focus">验收和兜底</span>。</div>
|
||||
</div>
|
||||
<div class="xw-footer"><span>白底|强重点|杂志竖排</span><span>Cover · 01</span></div>
|
||||
</section>
|
||||
|
||||
<!-- 2. SECTION DIVIDER -->
|
||||
<section class="slide">
|
||||
<div class="xw-topline"></div>
|
||||
<div class="xw-topbar">
|
||||
<div class="xw-tag"><span class="dot"></span>Chapter · 01</div>
|
||||
<div class="xw-page">02 / 08</div>
|
||||
</div>
|
||||
<div style="margin-top:120px">
|
||||
<div class="xw-kicker" style="font-size:20px;letter-spacing:.2em;text-transform:uppercase;color:#98a2b3">第一章</div>
|
||||
<h1 class="xw-title" style="font-size:110px;margin-top:20px">先看 <span class="xw-grad">大趋势</span></h1>
|
||||
<p class="xw-sub" style="font-size:28px">当执行越来越便宜,判断就会越来越贵。</p>
|
||||
</div>
|
||||
<div class="xw-footer"><span>Section Divider</span><span>02 / 08</span></div>
|
||||
</section>
|
||||
|
||||
<!-- 3. CONTENT — 4 card grid -->
|
||||
<section class="slide">
|
||||
<div class="xw-topline"></div>
|
||||
<div class="xw-topbar">
|
||||
<div class="xw-tag"><span class="dot"></span>越来越多的事会交给 AI</div>
|
||||
<div class="xw-page">03 / 08</div>
|
||||
</div>
|
||||
<h2 class="xw-title-md">未来 3 年,这些事都会 <span class="xw-grad">自动跑</span></h2>
|
||||
<div class="xw-grid-2">
|
||||
<div class="xw-card soft-pink"><div class="xw-label">内容</div><div class="main">写文案 · 写方案 · 写脚本</div><div class="desc">创作变成一个 prompt 的距离</div></div>
|
||||
<div class="xw-card soft-blue"><div class="xw-label">生产</div><div class="main">做图 · 搭页面 · 做表格</div><div class="desc">生产力工具集体重写一次</div></div>
|
||||
<div class="xw-card soft-green"><div class="xw-label">执行</div><div class="main">跑流程 · 写代码 · 自动操作</div><div class="desc">Agent 从 demo 走进真实工作流</div></div>
|
||||
<div class="xw-card soft-orange"><div class="xw-label">分析</div><div class="main">读数据 · 做总结 · 给建议</div><div class="desc">决策支持层彻底向下延伸</div></div>
|
||||
</div>
|
||||
<div class="xw-footer"><span>Content · Grid 2x2</span><span>03 / 08</span></div>
|
||||
</section>
|
||||
|
||||
<!-- 4. STEPS -->
|
||||
<section class="slide">
|
||||
<div class="xw-topline"></div>
|
||||
<div class="xw-topbar">
|
||||
<div class="xw-tag"><span class="dot"></span>为什么会这样</div>
|
||||
<div class="xw-page">04 / 08</div>
|
||||
</div>
|
||||
<h2 class="xw-title-md">AI 越强,<span class="xw-grad">判断对错</span> 越值钱</h2>
|
||||
<div class="xw-steps">
|
||||
<div class="xw-step"><div class="xw-num">1</div><div class="xw-txt">生产会更便宜,边际成本接近零</div></div>
|
||||
<div class="xw-step"><div class="xw-num">2</div><div class="xw-txt">复制会更快,错误也一起被加速</div></div>
|
||||
<div class="xw-step"><div class="xw-num">3</div><div class="xw-txt">AI 一本正经地做错,人类难以察觉</div></div>
|
||||
<div class="xw-step"><div class="xw-num">4</div><div class="xw-txt">所以最贵的能力会变成 <span class="xw-focus">发现问题</span></div></div>
|
||||
</div>
|
||||
<div class="xw-hero"><div class="xw-quote" style="font-size:30px">AI 让「<span class="xw-focus-blue">做出来</span>」变便宜,<br>但让「<span class="xw-focus">做对、做稳、别出事</span>」变更贵。</div></div>
|
||||
<div class="xw-footer"><span>Content · Steps</span><span>04 / 08</span></div>
|
||||
</section>
|
||||
|
||||
<!-- 5. CODE EXAMPLE -->
|
||||
<section class="slide">
|
||||
<div class="xw-topline"></div>
|
||||
<div class="xw-topbar">
|
||||
<div class="xw-tag"><span class="dot"></span>一段你今晚就能跑的验收 Skill</div>
|
||||
<div class="xw-page">05 / 08</div>
|
||||
</div>
|
||||
<h2 class="xw-title-md">不是写 prompt,<br>是写 <span class="xw-grad">验收清单</span></h2>
|
||||
<pre class="xw-codebox"><span class="cm"># skills/ai-acceptance/SKILL.md</span>
|
||||
<span class="kw">name</span>: <span class="st">ai-acceptance</span>
|
||||
<span class="kw">description</span>: <span class="st">"Runs AI output through a 4-gate review checklist."</span>
|
||||
|
||||
<span class="kw">gates</span>:
|
||||
- <span class="hl">functional</span>: <span class="st">"Does it actually do what the user asked?"</span>
|
||||
- <span class="hl">edge_cases</span>: <span class="st">"Empty / long / non-ASCII / concurrent?"</span>
|
||||
- <span class="hl">safety</span>: <span class="st">"PII, secrets, destructive ops — all red-flagged?"</span>
|
||||
- <span class="hl">rollback</span>: <span class="st">"If this ships and breaks, can we undo in 60s?"</span></pre>
|
||||
<div class="xw-footer"><span>Content · Code Block</span><span>05 / 08</span></div>
|
||||
</section>
|
||||
|
||||
<!-- 6. CHART — SVG bar -->
|
||||
<section class="slide">
|
||||
<div class="xw-topline"></div>
|
||||
<div class="xw-topbar">
|
||||
<div class="xw-tag"><span class="dot"></span>岗位相对价值变化</div>
|
||||
<div class="xw-page">06 / 08</div>
|
||||
</div>
|
||||
<h2 class="xw-title-md">越来越 <span class="xw-focus-pink">便宜</span>,越来越 <span class="xw-focus-green">贵</span></h2>
|
||||
<svg viewBox="0 0 960 380" style="width:100%;max-width:1000px;margin-top:30px" xmlns="http://www.w3.org/2000/svg">
|
||||
<g font-family="Inter, sans-serif" font-size="16" fill="#475467">
|
||||
<!-- baseline -->
|
||||
<line x1="180" y1="330" x2="940" y2="330" stroke="#eaecf3" stroke-width="2"/>
|
||||
<!-- rows -->
|
||||
<g transform="translate(0,40)">
|
||||
<text x="170" y="30" text-anchor="end" font-weight="700" fill="#111">纯执行</text>
|
||||
<rect x="180" y="10" width="520" height="28" rx="14" fill="#fff0f6"/>
|
||||
<rect x="180" y="10" width="120" height="28" rx="14" fill="#ff5fa2"/>
|
||||
<text x="710" y="30" fill="#c11574" font-weight="700">-65% 价值</text>
|
||||
</g>
|
||||
<g transform="translate(0,100)">
|
||||
<text x="170" y="30" text-anchor="end" font-weight="700" fill="#111">内容生产</text>
|
||||
<rect x="180" y="10" width="520" height="28" rx="14" fill="#eef4ff"/>
|
||||
<rect x="180" y="10" width="200" height="28" rx="14" fill="#4e8cff"/>
|
||||
<text x="710" y="30" fill="#174ea6" font-weight="700">-40% 价值</text>
|
||||
</g>
|
||||
<g transform="translate(0,160)">
|
||||
<text x="170" y="30" text-anchor="end" font-weight="700" fill="#111">数据分析</text>
|
||||
<rect x="180" y="10" width="520" height="28" rx="14" fill="#fff5ea"/>
|
||||
<rect x="180" y="10" width="320" height="28" rx="14" fill="#ff9d42"/>
|
||||
<text x="710" y="30" fill="#b54708" font-weight="700">持平</text>
|
||||
</g>
|
||||
<g transform="translate(0,220)">
|
||||
<text x="170" y="30" text-anchor="end" font-weight="700" fill="#111">测试 / 验收</text>
|
||||
<rect x="180" y="10" width="520" height="28" rx="14" fill="#edfdf3"/>
|
||||
<rect x="180" y="10" width="440" height="28" rx="14" fill="#17b26a"/>
|
||||
<text x="710" y="30" fill="#067647" font-weight="700">+85% 价值</text>
|
||||
</g>
|
||||
<g transform="translate(0,280)">
|
||||
<text x="170" y="30" text-anchor="end" font-weight="700" fill="#111">安全 / 风控</text>
|
||||
<rect x="180" y="10" width="520" height="28" rx="14" fill="#f4efff"/>
|
||||
<rect x="180" y="10" width="500" height="28" rx="14" fill="#7b61ff"/>
|
||||
<text x="710" y="30" fill="#5b21b6" font-weight="700">+110% 价值</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
<div class="xw-footer"><span>Chart · Horizontal Bars</span><span>06 / 08</span></div>
|
||||
</section>
|
||||
|
||||
<!-- 7. CTA -->
|
||||
<section class="slide">
|
||||
<div class="xw-topline"></div>
|
||||
<div class="xw-topbar">
|
||||
<div class="xw-tag"><span class="dot"></span>今晚就可以做的三件事</div>
|
||||
<div class="xw-page">07 / 08</div>
|
||||
</div>
|
||||
<h2 class="xw-title-md">别再追工具,<br>开始练 <span class="xw-grad">判断力</span></h2>
|
||||
<div class="xw-grid-3">
|
||||
<div class="xw-card soft-purple"><div class="xw-label">Tonight</div><div class="main">写一份<br>验收清单</div><div class="desc">哪怕只有 5 条,开始比完美更重要</div></div>
|
||||
<div class="xw-card soft-blue"><div class="xw-label">This week</div><div class="main">跑一遍<br>红队演练</div><div class="desc">对自己的 agent 说:试着让它出事</div></div>
|
||||
<div class="xw-card soft-green"><div class="xw-label">This month</div><div class="main">加一条<br>回滚流程</div><div class="desc">60 秒内能撤销,你就敢把手放开</div></div>
|
||||
</div>
|
||||
<div class="xw-hero"><div class="xw-quote" style="font-size:32px">真正的稀缺,不是「会用 AI」,<br>而是 <span class="xw-focus">「敢为 AI 的结果签字」</span>。</div></div>
|
||||
<div class="xw-footer"><span>CTA</span><span>07 / 08</span></div>
|
||||
</section>
|
||||
|
||||
<!-- 8. THANKS -->
|
||||
<section class="slide">
|
||||
<div class="xw-topline"></div>
|
||||
<div class="xw-topbar">
|
||||
<div class="xw-tag"><span class="dot"></span>Thanks for reading</div>
|
||||
<div class="xw-page">08 / 08</div>
|
||||
</div>
|
||||
<div style="margin-top:100px">
|
||||
<div class="xw-big-stat xw-grad">谢谢<small> · thanks</small></div>
|
||||
<p class="xw-sub" style="font-size:28px;margin-top:36px">如果你也在想这些问题,欢迎在评论里告诉我——<br>你最想让 AI 帮你做什么?你最不放心它做什么?</p>
|
||||
<div style="margin-top:40px">
|
||||
<span class="xw-pill">@lewis</span>
|
||||
<span class="xw-pill">小红书 · 白底杂志风</span>
|
||||
<span class="xw-pill">html-ppt · full-deck</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="xw-footer"><span>End</span><span>08 / 08</span></div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
12
skills/html-ppt/.clawscan-allow
Normal file
12
skills/html-ppt/.clawscan-allow
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# Security scan allowlist for html-ppt-skill
|
||||
# These patterns are false positives from template content, not actual threats.
|
||||
|
||||
# Path traversal: templates reference shared assets via relative paths
|
||||
# e.g. templates/full-decks/weekly-report/ → ../../../assets/
|
||||
# This is the correct relative path to the skill root assets directory.
|
||||
traversal:templates/full-decks/*/index.html
|
||||
|
||||
# Destructive commands: testing-safety-alert template displays forbidden
|
||||
# commands as text examples in a security policy demo slide.
|
||||
# They are HTML content, not executable code.
|
||||
destructive:templates/full-decks/testing-safety-alert/index.html
|
||||
21
skills/html-ppt/LICENSE
Normal file
21
skills/html-ppt/LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2026 lewis <sudolewis@gmail.com>
|
||||
|
||||
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.
|
||||
234
skills/html-ppt/README.md
Normal file
234
skills/html-ppt/README.md
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
# html-ppt — HTML PPT Studio
|
||||
|
||||
> A world-class AgentSkill for producing professional HTML presentations in
|
||||
> **36 themes**, **15 full-deck templates**, **31 page layouts**,
|
||||
> **47 animations** (27 CSS + 20 canvas FX), and a **true presenter mode**
|
||||
> with pixel-perfect previews + speaker script + timer — all pure static
|
||||
> HTML/CSS/JS, no build step.
|
||||
|
||||
**Author:** lewis <sudolewis@gmail.com>
|
||||
**License:** MIT
|
||||
**中文文档:** [README.zh-CN.md](README.zh-CN.md)
|
||||
|
||||

|
||||
|
||||
> One command installs **36 themes × 20 canvas FX × 31 layouts × 15 full decks + presenter mode**. Every preview above is a live iframe of a real template file rendering inside the deck — no screenshots, no mock-ups.
|
||||
|
||||
## 🎤 Presenter Mode (new!)
|
||||
|
||||
Press `S` on any deck to pop open a dedicated presenter window with four
|
||||
draggable, resizable **magnetic cards**: current slide, next slide preview,
|
||||
speaker script (逐字稿), and timer. Two windows stay in sync via
|
||||
`BroadcastChannel`.
|
||||
|
||||

|
||||
|
||||
**Why previews are pixel-perfect:** each card is an `<iframe>` that loads the
|
||||
same deck HTML with a `?preview=N` query param. The runtime detects this and
|
||||
renders only slide N with no chrome — so the preview uses the **same CSS,
|
||||
theme, fonts and viewport** as the audience view. Colors and layout are
|
||||
guaranteed identical.
|
||||
|
||||
**Smooth (no-reload) navigation:** on slide change, the presenter window
|
||||
sends `postMessage({type:'preview-goto', idx:N})` to each iframe. The iframe
|
||||
just toggles `.is-active` between slides — **no reload, no flicker**.
|
||||
|
||||
**Speaker script rules (3 golden):**
|
||||
1. **Prompt signals, not lines to read** — bold the keywords, separate
|
||||
transition sentences into their own paragraphs
|
||||
2. **150–300 words per slide** — that's the ~2–3 min/page pace
|
||||
3. **Write it like you speak** — conversational, not written prose
|
||||
|
||||
See [`references/presenter-mode.md`](references/presenter-mode.md) for the
|
||||
full authoring guide, or copy the ready-made template at
|
||||
`templates/full-decks/presenter-mode-reveal/` which ships with full 150-300
|
||||
word speaker scripts on every slide.
|
||||
|
||||
## Install (one command)
|
||||
|
||||
```bash
|
||||
npx skills add https://github.com/lewislulu/html-ppt-skill
|
||||
```
|
||||
|
||||
That registers the skill with your agent runtime. After install, any agent
|
||||
that supports AgentSkills can author presentations by asking things like:
|
||||
|
||||
> "做一份 8 页的技术分享 slides,用 cyberpunk 主题"
|
||||
> "turn this outline into a pitch deck"
|
||||
> "做一个小红书图文,9 张,白底柔和风"
|
||||
|
||||
## What's in the box
|
||||
|
||||
| | Count | Where |
|
||||
|---|---|---|
|
||||
| 🎤 **Presenter mode** | **NEW** | `S` key / `?preview=N` |
|
||||
| 🎨 **Themes** | **36** | `assets/themes/*.css` |
|
||||
| 📑 **Full-deck templates** | **15** | `templates/full-decks/<name>/` |
|
||||
| 🧩 **Single-page layouts** | **31** | `templates/single-page/*.html` |
|
||||
| ✨ **CSS animations** | **27** | `assets/animations/animations.css` |
|
||||
| 💥 **Canvas FX animations** | **20** | `assets/animations/fx/*.js` |
|
||||
| 🖼️ **Showcase decks** | 4 | `templates/*-showcase.html` |
|
||||
| 📸 **Verification screenshots** | 56 | `scripts/verify-output/` |
|
||||
|
||||
### 36 Themes
|
||||
|
||||
`minimal-white`, `editorial-serif`, `soft-pastel`, `sharp-mono`, `arctic-cool`,
|
||||
`sunset-warm`, `catppuccin-latte`, `catppuccin-mocha`, `dracula`, `tokyo-night`,
|
||||
`nord`, `solarized-light`, `gruvbox-dark`, `rose-pine`, `neo-brutalism`,
|
||||
`glassmorphism`, `bauhaus`, `swiss-grid`, `terminal-green`, `xiaohongshu-white`,
|
||||
`rainbow-gradient`, `aurora`, `blueprint`, `memphis-pop`, `cyberpunk-neon`,
|
||||
`y2k-chrome`, `retro-tv`, `japanese-minimal`, `vaporwave`, `midcentury`,
|
||||
`corporate-clean`, `academic-paper`, `news-broadcast`, `pitch-deck-vc`,
|
||||
`magazine-bold`, `engineering-whiteprint`.
|
||||
|
||||

|
||||
|
||||
Each is a pure CSS-tokens file — swap one `<link>` to reskin the entire deck.
|
||||
Browse them all in `templates/theme-showcase.html` (each slide rendered in an
|
||||
isolated iframe so theme ≠ theme is visually guaranteed).
|
||||
|
||||

|
||||
|
||||
### 15 Full-deck templates
|
||||
|
||||
Eight extracted from real-world decks, seven generic scenario scaffolds:
|
||||
|
||||
**Extracted looks**
|
||||
- `xhs-white-editorial` — 小红书白底杂志风
|
||||
- `graphify-dark-graph` — 暗底 + 力导向知识图谱
|
||||
- `knowledge-arch-blueprint` — 蓝图 / 架构图风
|
||||
- `hermes-cyber-terminal` — 终端 cyberpunk
|
||||
- `obsidian-claude-gradient` — 紫色渐变卡
|
||||
- `testing-safety-alert` — 红 / 琥珀警示风
|
||||
- `xhs-pastel-card` — 柔和马卡龙图文
|
||||
- `dir-key-nav-minimal` — 方向键极简
|
||||
|
||||
**Scenario decks**
|
||||
- `pitch-deck`, `product-launch`, `tech-sharing`, `weekly-report`,
|
||||
`xhs-post` (9-slide 3:4), `course-module`,
|
||||
**`presenter-mode-reveal`** 🎤 — complete talk template with full 150-300
|
||||
word speaker scripts on every slide, designed around the `S` key presenter mode
|
||||
|
||||
Each is a self-contained folder with scoped `.tpl-<name>` CSS so multiple
|
||||
decks can be previewed side-by-side without collisions. Browse the full
|
||||
gallery in `templates/full-decks-index.html`.
|
||||
|
||||

|
||||
|
||||
### 31 Single-page layouts
|
||||
|
||||
cover · toc · section-divider · bullets · two-column · three-column ·
|
||||
big-quote · stat-highlight · kpi-grid · table · code · diff · terminal ·
|
||||
flow-diagram · timeline · roadmap · mindmap · comparison · pros-cons ·
|
||||
todo-checklist · gantt · image-hero · image-grid · chart-bar · chart-line ·
|
||||
chart-pie · chart-radar · arch-diagram · process-steps · cta · thanks
|
||||
|
||||
Every layout ships with realistic demo data so you can drop it into a deck
|
||||
and immediately see it render.
|
||||
|
||||

|
||||
|
||||
*The big iframe is loading `templates/single-page/<name>.html` directly and cycling through all 31 layouts every 2.8 seconds.*
|
||||
|
||||

|
||||
|
||||
### 27 CSS animations + 20 Canvas FX
|
||||
|
||||
**CSS (lightweight)** — directional fades, `rise-in`, `zoom-pop`, `blur-in`,
|
||||
`glitch-in`, `typewriter`, `neon-glow`, `shimmer-sweep`, `gradient-flow`,
|
||||
`stagger-list`, `counter-up`, `path-draw`, `morph-shape`, `parallax-tilt`,
|
||||
`card-flip-3d`, `cube-rotate-3d`, `page-turn-3d`, `perspective-zoom`,
|
||||
`marquee-scroll`, `kenburns`, `ripple-reveal`, `spotlight`, …
|
||||
|
||||
**Canvas FX (cinematic)** — `particle-burst`, `confetti-cannon`, `firework`,
|
||||
`starfield`, `matrix-rain`, `knowledge-graph` (force-directed physics),
|
||||
`neural-net` (signal pulses), `constellation`, `orbit-ring`, `galaxy-swirl`,
|
||||
`word-cascade`, `letter-explode`, `chain-react`, `magnetic-field`,
|
||||
`data-stream`, `gradient-blob`, `sparkle-trail`, `shockwave`,
|
||||
`typewriter-multi`, `counter-explosion`. Each is a real hand-rolled canvas
|
||||
module auto-initialised on slide enter via `fx-runtime.js`.
|
||||
|
||||
## Quick start (manual, after install or git clone)
|
||||
|
||||
```bash
|
||||
# Scaffold a new deck from the base template
|
||||
./scripts/new-deck.sh my-talk
|
||||
|
||||
# Browse everything
|
||||
open templates/theme-showcase.html # all 36 themes (iframe-isolated)
|
||||
open templates/layout-showcase.html # all 31 layouts
|
||||
open templates/animation-showcase.html # all 47 animations
|
||||
open templates/full-decks-index.html # all 14 full decks
|
||||
|
||||
# Render any template to PNG via headless Chrome
|
||||
./scripts/render.sh templates/theme-showcase.html
|
||||
./scripts/render.sh examples/my-talk/index.html 12
|
||||
```
|
||||
|
||||
## Keyboard cheat sheet
|
||||
|
||||
```
|
||||
← → Space PgUp PgDn Home End navigate
|
||||
F fullscreen
|
||||
S open presenter window (magnetic cards)
|
||||
N quick notes drawer (bottom)
|
||||
R reset timer (in presenter window)
|
||||
O slide overview grid
|
||||
T cycle themes (syncs to presenter)
|
||||
A cycle a demo animation on current slide
|
||||
#/N (URL) deep-link to slide N
|
||||
?preview=N (URL) preview-only mode (single slide, no chrome)
|
||||
```
|
||||
|
||||
## Project structure
|
||||
|
||||
```
|
||||
html-ppt-skill/
|
||||
├── SKILL.md agent-facing dispatcher
|
||||
├── README.md this file
|
||||
├── references/ detailed catalogs
|
||||
│ ├── themes.md 36 themes with when-to-use
|
||||
│ ├── layouts.md 31 layout types
|
||||
│ ├── animations.md 27 CSS + 20 FX catalog
|
||||
│ ├── full-decks.md 14 full-deck templates
|
||||
│ └── authoring-guide.md full workflow
|
||||
├── assets/
|
||||
│ ├── base.css shared tokens + primitives
|
||||
│ ├── fonts.css webfont imports
|
||||
│ ├── runtime.js keyboard + presenter + overview
|
||||
│ ├── themes/*.css 36 theme token files
|
||||
│ └── animations/
|
||||
│ ├── animations.css 27 named CSS animations
|
||||
│ ├── fx-runtime.js auto-init [data-fx] on slide enter
|
||||
│ └── fx/*.js 20 canvas FX modules
|
||||
├── templates/
|
||||
│ ├── deck.html minimal starter
|
||||
│ ├── theme-showcase.html iframe-isolated theme tour
|
||||
│ ├── layout-showcase.html all 31 layouts
|
||||
│ ├── animation-showcase.html 47 animation slides
|
||||
│ ├── full-decks-index.html 14-deck gallery
|
||||
│ ├── full-decks/<name>/ 14 scoped multi-slide decks
|
||||
│ └── single-page/*.html 31 layout files with demo data
|
||||
├── scripts/
|
||||
│ ├── new-deck.sh scaffold
|
||||
│ ├── render.sh headless Chrome → PNG
|
||||
│ └── verify-output/ 56 self-test screenshots
|
||||
└── examples/demo-deck/ complete working deck
|
||||
```
|
||||
|
||||
## Philosophy
|
||||
|
||||
- **Token-driven design system.** All color, radius, shadow, font decisions
|
||||
live in `assets/base.css` + the current theme file. Change one variable,
|
||||
the whole deck reflows tastefully.
|
||||
- **Iframe isolation for previews.** Theme / layout / full-deck showcases all
|
||||
use `<iframe>` per slide so each preview is a real, independent render.
|
||||
- **Zero build.** Pure static HTML/CSS/JS. CDN only for webfonts, highlight.js
|
||||
and chart.js (optional).
|
||||
- **Senior-designer defaults.** Opinionated type scale, spacing rhythm,
|
||||
gradients and card treatments — no "Corporate PowerPoint 2006" vibes.
|
||||
- **Chinese + English first-class.** Noto Sans SC / Noto Serif SC pre-imported.
|
||||
|
||||
## License
|
||||
|
||||
MIT © 2026 lewis <sudolewis@gmail.com>.
|
||||
238
skills/html-ppt/README.zh-CN.md
Normal file
238
skills/html-ppt/README.zh-CN.md
Normal file
|
|
@ -0,0 +1,238 @@
|
|||
# html-ppt · HTML PPT 工作室
|
||||
|
||||
> 一款专业级的 AgentSkill,让 AI 做出真正能打的 HTML 演示文稿。
|
||||
> **36 套主题**、**15 套完整 deck 模板**、**31 种页面布局**、**47 个动效**
|
||||
> (27 个 CSS + 20 个 Canvas FX),加上全新的 **演讲者模式** —— 像素级
|
||||
> 完美预览 + 逐字稿提词器 + 计时器。纯静态 HTML/CSS/JS,无需构建。
|
||||
|
||||
**作者:** lewis <sudolewis@gmail.com>
|
||||
**协议:** MIT
|
||||
**English docs:** [README.md](README.md)
|
||||
|
||||

|
||||
|
||||
> 一行命令装好 **36 主题 × 20 Canvas FX × 31 布局 × 15 完整 deck + 演讲者模式**。
|
||||
> 上图里的每一个预览都是真实的 iframe 加载真实模板文件 —— 不是截图,不是色卡。
|
||||
|
||||
## 🎤 演讲者模式(全新)
|
||||
|
||||
在任何 deck 里按 `S` 键,弹出一个独立的演讲者窗口,包含 4 个**可拖拽、
|
||||
可调整大小的磁吸卡片**:当前页预览、下一页预览、逐字稿、计时器。两个窗口
|
||||
通过 `BroadcastChannel` 双向同步翻页。
|
||||
|
||||

|
||||
|
||||
**为什么预览是像素级完美的:** 每个卡片是一个 `<iframe>`,加载的是**同一
|
||||
份 deck HTML 文件**,只是 URL 多了 `?preview=N` 参数。runtime 检测到这个
|
||||
参数后,只渲染第 N 页并隐藏所有 chrome —— 所以预览使用**和观众视图完全相
|
||||
同的 CSS、主题、字体、viewport**,颜色和排版保证 100% 一致。
|
||||
|
||||
**丝滑翻页(零闪烁):** 翻页时演讲者窗口通过 `postMessage({type:'preview-goto',
|
||||
idx:N})` 通知 iframe,iframe 只是切换 `.is-active` class —— **不重新加载、
|
||||
不白屏、不闪烁**。
|
||||
|
||||
**逐字稿 3 条铁律:**
|
||||
1. **提示信号,不是讲稿** — 关键词加粗,过渡句独立成段
|
||||
2. **每页 150–300 字** — 约 2–3 分钟/页的节奏
|
||||
3. **用口语,不用书面语** — "所以" 不是 "因此","这个" 不是 "该"
|
||||
|
||||
详见 [`references/presenter-mode.md`](references/presenter-mode.md),或直接复制
|
||||
`templates/full-decks/presenter-mode-reveal/` 这个现成模板 —— 每一页都带完整
|
||||
150–300 字的示例逐字稿。
|
||||
|
||||
## 一行命令安装
|
||||
|
||||
```bash
|
||||
npx skills add https://github.com/lewislulu/html-ppt-skill
|
||||
```
|
||||
|
||||
装好后,任何支持 AgentSkill 的 agent(Claude Code / Codex / Cursor / OpenClaw 等)
|
||||
都能用这套能力做 PPT。对 agent 说:
|
||||
|
||||
> "做一份 8 页的技术分享 slides,用 cyberpunk 主题"
|
||||
> "把这段 outline 变成投资人 pitch deck"
|
||||
> "做一个小红书图文,9 张,白底柔和风"
|
||||
> "做一份带演讲者模式的产品分享,我想要有逐字稿"
|
||||
|
||||
## Skill 内容一览
|
||||
|
||||
| | 数量 | 位置 |
|
||||
|---|---|---|
|
||||
| 🎤 **演讲者模式** | **新增** | `S` 键 / `?preview=N` |
|
||||
| 🎨 **主题** | **36** | `assets/themes/*.css` |
|
||||
| 📑 **完整 deck 模板** | **15** | `templates/full-decks/<name>/` |
|
||||
| 🧩 **单页布局** | **31** | `templates/single-page/*.html` |
|
||||
| ✨ **CSS 动画** | **27** | `assets/animations/animations.css` |
|
||||
| 💥 **Canvas FX 动画** | **20** | `assets/animations/fx/*.js` |
|
||||
| 🖼️ **Showcase deck** | 4 | `templates/*-showcase.html` |
|
||||
| 📸 **验证截图** | 56 | `scripts/verify-output/` |
|
||||
|
||||
### 36 套主题
|
||||
|
||||
`minimal-white`、`editorial-serif`、`soft-pastel`、`sharp-mono`、`arctic-cool`、
|
||||
`sunset-warm`、`catppuccin-latte`、`catppuccin-mocha`、`dracula`、`tokyo-night`、
|
||||
`nord`、`solarized-light`、`gruvbox-dark`、`rose-pine`、`neo-brutalism`、
|
||||
`glassmorphism`、`bauhaus`、`swiss-grid`、`terminal-green`、`xiaohongshu-white`、
|
||||
`rainbow-gradient`、`aurora`、`blueprint`、`memphis-pop`、`cyberpunk-neon`、
|
||||
`y2k-chrome`、`retro-tv`、`japanese-minimal`、`vaporwave`、`midcentury`、
|
||||
`corporate-clean`、`academic-paper`、`news-broadcast`、`pitch-deck-vc`、
|
||||
`magazine-bold`、`engineering-whiteprint`
|
||||
|
||||

|
||||
|
||||
每个主题都是一份纯 CSS token 文件 —— 只需要换一行 `<link>` 就能给整份 deck
|
||||
换皮。在 `templates/theme-showcase.html` 里可以浏览全部(每一页用独立 iframe
|
||||
渲染,避免样式互相污染)。
|
||||
|
||||

|
||||
|
||||
### 15 套完整 deck 模板
|
||||
|
||||
8 个从真实作品提炼的视觉语言,7 个通用场景脚手架:
|
||||
|
||||
**提炼款**
|
||||
- `xhs-white-editorial` — 小红书白底杂志风
|
||||
- `graphify-dark-graph` — 暗底 + 力导向知识图谱
|
||||
- `knowledge-arch-blueprint` — 蓝图 / 架构图风
|
||||
- `hermes-cyber-terminal` — 终端 cyberpunk 风
|
||||
- `obsidian-claude-gradient` — 紫色渐变卡
|
||||
- `testing-safety-alert` — 红 / 琥珀警示风
|
||||
- `xhs-pastel-card` — 柔和马卡龙图文
|
||||
- `dir-key-nav-minimal` — 方向键极简
|
||||
|
||||
**场景款**
|
||||
- `pitch-deck` — 投资人 pitch
|
||||
- `product-launch` — 产品发布会
|
||||
- `tech-sharing` — 技术分享
|
||||
- `weekly-report` — 周报
|
||||
- `xhs-post` — 小红书图文(9 页 3:4)
|
||||
- `course-module` — 教学模块
|
||||
- **`presenter-mode-reveal`** 🎤 — 完整分享模板,**每一页都带 150-300 字
|
||||
的示例逐字稿**,围绕 `S` 键演讲者模式专门设计
|
||||
|
||||
每个模板都是自包含的文件夹,用 scoped `.tpl-<name>` CSS,所以多个模板可以
|
||||
同时加载不会互相污染。在 `templates/full-decks-index.html` 可以看全套 gallery。
|
||||
|
||||

|
||||
|
||||
### 31 种单页布局
|
||||
|
||||
cover · toc · section-divider · bullets · two-column · three-column ·
|
||||
big-quote · stat-highlight · kpi-grid · table · code · diff · terminal ·
|
||||
flow-diagram · timeline · roadmap · mindmap · comparison · pros-cons ·
|
||||
todo-checklist · gantt · image-hero · image-grid · chart-bar · chart-line ·
|
||||
chart-pie · chart-radar · arch-diagram · process-steps · cta · thanks
|
||||
|
||||
每个布局都带真实的示例数据,拖进 deck 立即看得到效果。
|
||||
|
||||

|
||||
|
||||
*大 iframe 直接加载 `templates/single-page/<name>.html` 文件,每 2.8 秒
|
||||
自动切换到下一个布局。*
|
||||
|
||||

|
||||
|
||||
### 27 个 CSS 动画 + 20 个 Canvas FX
|
||||
|
||||
**CSS 动画(轻量)** — 方向性淡入、`rise-in`、`zoom-pop`、`blur-in`、
|
||||
`glitch-in`、`typewriter`(打字机)、`neon-glow`(霓虹光晕)、
|
||||
`shimmer-sweep`(流光)、`gradient-flow`(渐变流动)、`stagger-list`
|
||||
(列表错开入场)、`counter-up`(数字滚动)、`path-draw`(路径绘制)、
|
||||
`morph-shape`、`parallax-tilt`、`card-flip-3d`、`cube-rotate-3d`、
|
||||
`page-turn-3d`、`perspective-zoom`、`marquee-scroll`、`kenburns`、
|
||||
`ripple-reveal`、`spotlight`、…
|
||||
|
||||
**Canvas FX(电影级)** — `particle-burst`(粒子爆发)、`confetti-cannon`
|
||||
(彩带)、`firework`(烟花)、`starfield`(星空)、`matrix-rain`
|
||||
(代码雨)、`knowledge-graph`(力导向知识图谱)、`neural-net`(神经网络
|
||||
脉冲)、`constellation`(星座连线)、`orbit-ring`(轨道环)、
|
||||
`galaxy-swirl`(星系漩涡)、`word-cascade`、`letter-explode`、
|
||||
`chain-react`、`magnetic-field`、`data-stream`、`gradient-blob`、
|
||||
`sparkle-trail`、`shockwave`、`typewriter-multi`、`counter-explosion`。
|
||||
每一个都是手写的 canvas 模块,进入 slide 时由 `fx-runtime.js` 自动初始化。
|
||||
|
||||
## 快速开始(手动 / 安装后 / git clone 后)
|
||||
|
||||
```bash
|
||||
# 从 base 模板新建一个 deck
|
||||
./scripts/new-deck.sh my-talk
|
||||
|
||||
# 浏览所有内容
|
||||
open templates/theme-showcase.html # 全部 36 主题(iframe 隔离)
|
||||
open templates/layout-showcase.html # 全部 31 布局
|
||||
open templates/animation-showcase.html # 全部 47 动效
|
||||
open templates/full-decks-index.html # 全部 15 个完整 deck
|
||||
|
||||
# 用 headless Chrome 导出 PNG
|
||||
./scripts/render.sh templates/theme-showcase.html
|
||||
./scripts/render.sh examples/my-talk/index.html 12
|
||||
```
|
||||
|
||||
## 键盘快捷键
|
||||
|
||||
```
|
||||
← → Space PgUp PgDn Home End 翻页
|
||||
F 全屏
|
||||
S 打开演讲者窗口(磁吸卡片模式)
|
||||
N 底部 notes 抽屉
|
||||
R 重置计时器(演讲者窗口内)
|
||||
O slide 总览网格
|
||||
T 切换主题(自动同步到演讲者窗口)
|
||||
A 在当前 slide 循环演示一个动画
|
||||
#/N (URL) 深链到第 N 页
|
||||
?preview=N (URL) 预览模式(只显示单页,隐藏 chrome)
|
||||
```
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
html-ppt-skill/
|
||||
├── SKILL.md agent 入口
|
||||
├── README.md 英文 README
|
||||
├── README.zh-CN.md 本文件
|
||||
├── references/ 详细文档
|
||||
│ ├── themes.md 36 主题 + 使用场景
|
||||
│ ├── layouts.md 31 布局
|
||||
│ ├── animations.md 27 CSS + 20 FX 目录
|
||||
│ ├── full-decks.md 15 完整 deck 模板
|
||||
│ ├── presenter-mode.md 🎤 演讲者模式 + 逐字稿指南
|
||||
│ └── authoring-guide.md 完整工作流
|
||||
├── assets/
|
||||
│ ├── base.css 共享 tokens + 基础组件
|
||||
│ ├── fonts.css web 字体引入
|
||||
│ ├── runtime.js 键盘导航 + 演讲者模式 + 总览
|
||||
│ ├── themes/*.css 36 主题 token 文件
|
||||
│ └── animations/
|
||||
│ ├── animations.css 27 个命名 CSS 动画
|
||||
│ ├── fx-runtime.js 进入 slide 自动初始化 [data-fx]
|
||||
│ └── fx/*.js 20 个 Canvas FX 模块
|
||||
├── templates/
|
||||
│ ├── deck.html 最小起步模板
|
||||
│ ├── theme-showcase.html iframe 隔离的主题 tour
|
||||
│ ├── layout-showcase.html 全部 31 布局
|
||||
│ ├── animation-showcase.html 47 动画 slide
|
||||
│ ├── full-decks-index.html 15 deck gallery
|
||||
│ ├── full-decks/<name>/ 15 个 scoped 多页 deck 模板
|
||||
│ └── single-page/*.html 31 个布局文件(带示例数据)
|
||||
├── scripts/
|
||||
│ ├── new-deck.sh 脚手架
|
||||
│ ├── render.sh headless Chrome → PNG
|
||||
│ └── verify-output/ 56 张自测截图
|
||||
└── examples/demo-deck/ 完整可运行的示例 deck
|
||||
```
|
||||
|
||||
## 设计理念
|
||||
|
||||
- **Token 驱动的设计系统。** 所有颜色、圆角、阴影、字体决策都在
|
||||
`assets/base.css` + 当前主题文件里。改一个变量,整份 deck 优雅地重排。
|
||||
- **Iframe 隔离预览。** 主题 / 布局 / 完整 deck 的 showcase 都用 `<iframe>`,
|
||||
确保每个预览都是真实、独立的渲染结果。
|
||||
- **零构建。** 纯静态 HTML/CSS/JS。只有 webfont / highlight.js / chart.js
|
||||
(可选) 走 CDN。
|
||||
- **资深设计师的默认值。** 字号规律、间距节奏、渐变、卡片处理都有态度 ——
|
||||
绝不是 "PowerPoint 2006" 那种味道。
|
||||
- **中英双语一等公民。** 预导入了 Noto Sans SC / Noto Serif SC。
|
||||
|
||||
## 协议
|
||||
|
||||
MIT © 2026 lewis <sudolewis@gmail.com>
|
||||
251
skills/html-ppt/SKILL.md
Normal file
251
skills/html-ppt/SKILL.md
Normal file
|
|
@ -0,0 +1,251 @@
|
|||
---
|
||||
name: html-ppt
|
||||
description: HTML PPT Studio — author professional static HTML presentations in many styles, layouts, and animations, all driven by templates. Use when the user asks for a presentation, PPT, slides, keynote, deck, slideshow, "幻灯片", "演讲稿", "做一份 PPT", "做一份 slides", a reveal-style HTML deck, a 小红书 图文, or any kind of multi-slide pitch/report/sharing document that should look tasteful and be usable with keyboard navigation. Triggers include keywords like "presentation", "ppt", "slides", "deck", "keynote", "reveal", "slideshow", "幻灯片", "演讲稿", "分享稿", "小红书图文", "talk slides", "pitch deck", "tech sharing", "technical presentation".
|
||||
triggers:
|
||||
- "ppt"
|
||||
- "deck"
|
||||
- "slides"
|
||||
- "presentation"
|
||||
- "keynote"
|
||||
- "reveal"
|
||||
- "slideshow"
|
||||
- "幻灯片"
|
||||
- "演讲稿"
|
||||
- "分享稿"
|
||||
- "talk slides"
|
||||
- "pitch deck"
|
||||
- "tech sharing"
|
||||
- "technical presentation"
|
||||
od:
|
||||
mode: deck
|
||||
scenario: marketing
|
||||
featured: 19
|
||||
upstream: "https://github.com/lewislulu/html-ppt-skill"
|
||||
preview:
|
||||
type: html
|
||||
entry: index.html
|
||||
design_system:
|
||||
requires: false
|
||||
speaker_notes: true
|
||||
animations: true
|
||||
example_prompt: "用 html-ppt 做一份 12 页的 HTML PPT。先帮我确认三件事:内容/页数/受众、主题(从 36 套里推荐 2-3 个)、起点全 deck 模板(pitch-deck / tech-sharing / weekly-report / xhs-post / presenter-mode-reveal 任选一个),对齐之后再开始写 slides。"
|
||||
---
|
||||
|
||||
# html-ppt — HTML PPT Studio
|
||||
|
||||
Author professional HTML presentations as static files. One theme file = one
|
||||
look. One layout file = one page type. One animation class = one entry effect.
|
||||
All pages share a token-based design system in `assets/base.css`.
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
npx skills add https://github.com/lewislulu/html-ppt-skill
|
||||
```
|
||||
|
||||
One command, no build. Pure static HTML/CSS/JS with only CDN webfonts.
|
||||
|
||||
## What the skill gives you
|
||||
|
||||
- **36 themes** (`assets/themes/*.css`) — minimal-white, editorial-serif, soft-pastel, sharp-mono, arctic-cool, sunset-warm, catppuccin-latte/mocha, dracula, tokyo-night, nord, solarized-light, gruvbox-dark, rose-pine, neo-brutalism, glassmorphism, bauhaus, swiss-grid, terminal-green, xiaohongshu-white, rainbow-gradient, aurora, blueprint, memphis-pop, cyberpunk-neon, y2k-chrome, retro-tv, japanese-minimal, vaporwave, midcentury, corporate-clean, academic-paper, news-broadcast, pitch-deck-vc, magazine-bold, engineering-whiteprint
|
||||
- **15 full-deck templates** (`templates/full-decks/<name>/`) — complete multi-slide decks with scoped `.tpl-<name>` CSS. 8 extracted from real-world decks (xhs-white-editorial, graphify-dark-graph, knowledge-arch-blueprint, hermes-cyber-terminal, obsidian-claude-gradient, testing-safety-alert, xhs-pastel-card, dir-key-nav-minimal), 7 scenario scaffolds (pitch-deck, product-launch, tech-sharing, weekly-report, xhs-post 3:4, course-module, **presenter-mode-reveal** — 演讲者模式专用)
|
||||
- **31 layouts** (`templates/single-page/*.html`) with realistic demo data
|
||||
- **27 CSS animations** (`assets/animations/animations.css`) via `data-anim`
|
||||
- **20 canvas FX animations** (`assets/animations/fx/*.js`) via `data-fx` — particle-burst, confetti-cannon, firework, starfield, matrix-rain, knowledge-graph (force-directed), neural-net (pulses), constellation, orbit-ring, galaxy-swirl, word-cascade, letter-explode, chain-react, magnetic-field, data-stream, gradient-blob, sparkle-trail, shockwave, typewriter-multi, counter-explosion
|
||||
- **Keyboard runtime** (`assets/runtime.js`) — arrows, T (theme), A (anim), F/O, **S (presenter mode: magnetic-card popup with CURRENT / NEXT / SCRIPT / TIMER cards)**, N (notes drawer), R (reset timer in presenter)
|
||||
- **FX runtime** (`assets/animations/fx-runtime.js`) — auto-inits `[data-fx]` on slide enter, cleans up on leave
|
||||
- **Showcase decks** for themes / layouts / animations / full-decks gallery
|
||||
- **Headless Chrome render script** for PNG export
|
||||
|
||||
## When to use
|
||||
|
||||
Use when the user asks for any kind of slide-based output or wants to turn
|
||||
text/notes into a presentable deck. Prefer this over building from scratch.
|
||||
|
||||
### 🎤 Presenter Mode (演讲者模式 + 逐字稿)
|
||||
|
||||
If the user mentions any of: **演讲 / 分享 / 讲稿 / 逐字稿 / speaker notes / presenter view / 演讲者视图 / 提词器**, or says things like "我要去给团队讲 xxx", "要做一场技术分享", "怕讲不流畅", "想要一份带逐字稿的 PPT" — **use the `presenter-mode-reveal` full-deck template** and write 150–300 words of 逐字稿 in each slide's `<aside class="notes">`.
|
||||
|
||||
See [references/presenter-mode.md](references/presenter-mode.md) for the full authoring guide including the 3 rules of speaker script writing:
|
||||
1. **不是讲稿,是提示信号** — 加粗核心词 + 过渡句独立成段
|
||||
2. **每页 150–300 字** — 2–3 分钟/页的节奏
|
||||
3. **用口语,不用书面语** — "因此"→"所以","该方案"→"这个方案"
|
||||
|
||||
All full-deck templates support the S key presenter mode (it's built into `runtime.js`). **S opens a new popup window with 4 magnetic cards**:
|
||||
- 🔵 **CURRENT** — pixel-perfect iframe preview of the current slide
|
||||
- 🟣 **NEXT** — pixel-perfect iframe preview of the next slide
|
||||
- 🟠 **SPEAKER SCRIPT** — large-font 逐字稿 (scrollable)
|
||||
- 🟢 **TIMER** — elapsed time + slide counter + prev/next/reset buttons
|
||||
|
||||
Each card is **draggable by its header** and **resizable by the bottom-right corner handle**. Card positions/sizes persist to `localStorage` per deck. A "Reset layout" button restores the default arrangement.
|
||||
|
||||
**Why the previews are pixel-perfect**: each preview is an `<iframe>` that loads the actual deck HTML with a `?preview=N` query param; `runtime.js` detects this and renders only slide N with no chrome. So the preview uses the **same CSS, theme, fonts, and viewport as the audience view** — colors and layout are guaranteed identical.
|
||||
|
||||
**Smooth navigation**: on slide change, the presenter window sends `postMessage({type:'preview-goto', idx:N})` to each iframe. The iframe just toggles `.is-active` between slides — **no reload, no flicker**. The two windows also stay in sync via `BroadcastChannel`.
|
||||
|
||||
Only `presenter-mode-reveal` is designed from the ground up around the feature with proper example 逐字稿 on every slide.
|
||||
|
||||
Keyboard in presenter window: `← →` navigate (syncs audience) · `R` reset timer · `Esc` close popup.
|
||||
Keyboard in audience window: `S` open presenter · `T` cycle theme · `← →` navigate (syncs presenter) · `F` fullscreen · `O` overview.
|
||||
|
||||
## Before you author anything — ALWAYS ask or recommend
|
||||
|
||||
**Do not start writing slides until you understand three things.** Either ask
|
||||
the user directly, or — if they already handed you rich content — propose a
|
||||
tasteful default and confirm.
|
||||
|
||||
1. **Content & audience.** What's the deck about, how many slides, who's
|
||||
watching (engineers / execs / 小红书读者 / 学生 / VC)?
|
||||
2. **Style / theme.** Which of the 36 themes fits? If unsure, recommend 2-3
|
||||
candidates based on tone:
|
||||
- Business / investor pitch → `pitch-deck-vc`, `corporate-clean`, `swiss-grid`
|
||||
- Tech sharing / engineering → `tokyo-night`, `dracula`, `catppuccin-mocha`,
|
||||
`terminal-green`, `blueprint`
|
||||
- 小红书图文 → `xiaohongshu-white`, `soft-pastel`, `rainbow-gradient`,
|
||||
`magazine-bold`
|
||||
- Academic / report → `academic-paper`, `editorial-serif`, `minimal-white`
|
||||
- Edgy / cyber / launch → `cyberpunk-neon`, `vaporwave`, `y2k-chrome`,
|
||||
`neo-brutalism`
|
||||
3. **Starting point.** One of the 14 full-deck templates, or scratch? Point
|
||||
to the closest `templates/full-decks/<name>/` and ask if it fits. If the
|
||||
user's content suggests something obvious (e.g. "我要做产品发布会" →
|
||||
`product-launch`), propose it confidently instead of asking blindly.
|
||||
|
||||
A good opening message looks like:
|
||||
|
||||
> 我可以给你做这份 PPT!先确认三件事:
|
||||
> 1. 大致内容 / 页数 / 观众是谁?
|
||||
> 2. 风格偏好?我建议从这 3 个主题里选一个:`tokyo-night`(技术分享默认好看)、`xiaohongshu-white`(小红书风)、`corporate-clean`(正式汇报)。
|
||||
> 3. 要不要用我现成的 `tech-sharing` 全 deck 模板打底?
|
||||
|
||||
Only after those are clear, scaffold the deck and start writing.
|
||||
|
||||
## Quick start
|
||||
|
||||
1. **Scaffold a new deck.** From the repo root:
|
||||
```bash
|
||||
./scripts/new-deck.sh my-talk
|
||||
open examples/my-talk/index.html
|
||||
```
|
||||
2. **Pick a theme.** Open the deck and press `T` to cycle. Or hard-code it:
|
||||
```html
|
||||
<link rel="stylesheet" id="theme-link" href="../assets/themes/aurora.css">
|
||||
```
|
||||
Catalog in [references/themes.md](references/themes.md).
|
||||
3. **Pick layouts.** Copy `<section class="slide">...</section>` blocks out of
|
||||
files in `templates/single-page/` into your deck. Replace the demo data.
|
||||
Catalog in [references/layouts.md](references/layouts.md).
|
||||
4. **Add animations.** Put `data-anim="fade-up"` (or `class="anim-fade-up"`) on
|
||||
any element. On `<ul>`/grids, use `anim-stagger-list` for sequenced reveals.
|
||||
For canvas FX, use `<div data-fx="knowledge-graph">...</div>` and include
|
||||
`<script src="../assets/animations/fx-runtime.js"></script>`.
|
||||
Catalog in [references/animations.md](references/animations.md).
|
||||
5. **Use a full-deck template.** Copy `templates/full-decks/<name>/` into
|
||||
`examples/my-talk/` as a starting point. Each folder is self-contained with
|
||||
scoped CSS. Catalog in [references/full-decks.md](references/full-decks.md)
|
||||
and gallery at `templates/full-decks-index.html`.
|
||||
6. **Render to PNG.**
|
||||
```bash
|
||||
./scripts/render.sh templates/theme-showcase.html # one shot
|
||||
./scripts/render.sh examples/my-talk/index.html 12 # 12 slides
|
||||
```
|
||||
|
||||
## Authoring rules (important)
|
||||
|
||||
- **Always start from a template.** Don't author slides from scratch — copy the
|
||||
closest layout from `templates/single-page/` first, then replace content.
|
||||
- **Use tokens, not literal colors.** Every color, radius, shadow should come
|
||||
from CSS variables defined in `assets/base.css` and overridden by a theme.
|
||||
Good: `color: var(--text-1)`. Bad: `color: #111`.
|
||||
- **Don't invent new layout files.** Prefer composing existing ones. Only add
|
||||
a new `templates/single-page/*.html` if none of the 30 fit.
|
||||
- **Respect chrome slots.** `.deck-header`, `.deck-footer`, `.slide-number`
|
||||
and the progress bar are provided by `assets/base.css` + `runtime.js`.
|
||||
- **Keyboard-first.** Always include `<script src="../assets/runtime.js"></script>`
|
||||
so the deck supports ← → / T / A / F / S / O / hash deep-links.
|
||||
- **One `.slide` per logical page.** `runtime.js` makes `.slide.is-active`
|
||||
visible; all others are hidden.
|
||||
- **Supply notes.** Wrap speaker notes in `<div class="notes">…</div>` inside
|
||||
each slide. Press S to open the overlay.
|
||||
- **NEVER put presenter-only text on the slide itself.** Descriptive text like
|
||||
"这一页展示了……" or "Speaker: 这里可以补充……" or small explanatory captions
|
||||
aimed at the presenter MUST go inside `<div class="notes">`, NOT as visible
|
||||
`<p>` / `<span>` elements on the slide. The `.notes` class is `display:none`
|
||||
by default — it only appears in the S overlay. Slides should contain ONLY
|
||||
audience-facing content (titles, bullet points, data, charts, images).
|
||||
|
||||
## Writing guide
|
||||
|
||||
See [references/authoring-guide.md](references/authoring-guide.md) for a
|
||||
step-by-step walkthrough: file structure, naming, how to transform an outline
|
||||
into a deck, how to choose layouts and themes per audience, how to do a
|
||||
Chinese + English deck, and how to export.
|
||||
|
||||
## Catalogs (load when needed)
|
||||
|
||||
- [references/themes.md](references/themes.md) — all 36 themes with when-to-use.
|
||||
- [references/layouts.md](references/layouts.md) — all 31 layout types.
|
||||
- [references/animations.md](references/animations.md) — 27 CSS + 20 canvas FX animations.
|
||||
- [references/full-decks.md](references/full-decks.md) — all 15 full-deck templates.
|
||||
- [references/presenter-mode.md](references/presenter-mode.md) — **演讲者模式 + 逐字稿编写指南(技术分享/演讲必看)**.
|
||||
- [references/authoring-guide.md](references/authoring-guide.md) — full workflow.
|
||||
|
||||
## File structure
|
||||
|
||||
```
|
||||
html-ppt/
|
||||
├── SKILL.md (this file)
|
||||
├── references/ (detailed catalogs, load as needed)
|
||||
├── assets/
|
||||
│ ├── base.css (tokens + primitives — do not edit per deck)
|
||||
│ ├── fonts.css (webfont imports)
|
||||
│ ├── runtime.js (keyboard + presenter + overview + theme cycle)
|
||||
│ ├── themes/*.css (36 token overrides, one per theme)
|
||||
│ └── animations/
|
||||
│ ├── animations.css (27 named CSS entry animations)
|
||||
│ ├── fx-runtime.js (auto-init [data-fx] on slide enter)
|
||||
│ └── fx/*.js (20 canvas FX modules: particles/graph/fireworks…)
|
||||
├── templates/
|
||||
│ ├── deck.html (minimal 6-slide starter)
|
||||
│ ├── theme-showcase.html (36 slides, iframe-isolated per theme)
|
||||
│ ├── layout-showcase.html (iframe tour of all 31 layouts)
|
||||
│ ├── animation-showcase.html (20 FX + 27 CSS animation slides)
|
||||
│ ├── full-decks-index.html (gallery of all 14 full-deck templates)
|
||||
│ ├── full-decks/<name>/ (14 scoped multi-slide deck templates)
|
||||
│ └── single-page/*.html (31 layout files with demo data)
|
||||
├── scripts/
|
||||
│ ├── new-deck.sh (scaffold a deck from deck.html)
|
||||
│ └── render.sh (headless Chrome → PNG)
|
||||
└── examples/demo-deck/ (complete working deck)
|
||||
```
|
||||
|
||||
## Rendering to PNG
|
||||
|
||||
`scripts/render.sh` wraps headless Chrome at
|
||||
`/Applications/Google Chrome.app/Contents/MacOS/Google Chrome`. For multi-slide
|
||||
capture, runtime.js exposes `#/N` deep-links, and render.sh iterates 1..N.
|
||||
|
||||
```bash
|
||||
./scripts/render.sh templates/single-page/kpi-grid.html # single page
|
||||
./scripts/render.sh examples/demo-deck/index.html 8 out-dir # 8 slides, custom dir
|
||||
```
|
||||
|
||||
## Keyboard cheat sheet
|
||||
|
||||
```
|
||||
← → Space PgUp PgDn Home End navigate
|
||||
F fullscreen
|
||||
S open presenter window (magnetic cards: current/next/script/timer)
|
||||
N quick notes drawer (bottom overlay)
|
||||
R reset timer (in presenter window)
|
||||
?preview=N URL param — force preview-only mode (single slide, no chrome)
|
||||
O slide overview grid
|
||||
T cycle themes (reads data-themes attr)
|
||||
A cycle demo animation on current slide
|
||||
#/N in URL deep-link to slide N
|
||||
Esc close all overlays
|
||||
```
|
||||
|
||||
## License & author
|
||||
|
||||
MIT. Copyright (c) 2026 lewis <sudolewis@gmail.com>.
|
||||
138
skills/html-ppt/assets/animations/animations.css
Normal file
138
skills/html-ppt/assets/animations/animations.css
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
/* html-ppt :: animations.css
|
||||
* Apply by adding class="anim-<name>" or data-anim="<name>".
|
||||
* Durations are deliberately snappy; tweak --anim-dur per element.
|
||||
*/
|
||||
:root{--anim-dur:.7s;--anim-ease:cubic-bezier(.4,0,.2,1)}
|
||||
|
||||
/* ---------- FADE DIRECTIONALS ---------- */
|
||||
@keyframes kf-fade-up{from{opacity:0;transform:translateY(32px)}to{opacity:1;transform:none}}
|
||||
@keyframes kf-fade-down{from{opacity:0;transform:translateY(-32px)}to{opacity:1;transform:none}}
|
||||
@keyframes kf-fade-left{from{opacity:0;transform:translateX(-40px)}to{opacity:1;transform:none}}
|
||||
@keyframes kf-fade-right{from{opacity:0;transform:translateX(40px)}to{opacity:1;transform:none}}
|
||||
.anim-fade-up{animation:kf-fade-up var(--anim-dur) var(--anim-ease) both}
|
||||
.anim-fade-down{animation:kf-fade-down var(--anim-dur) var(--anim-ease) both}
|
||||
.anim-fade-left{animation:kf-fade-left var(--anim-dur) var(--anim-ease) both}
|
||||
.anim-fade-right{animation:kf-fade-right var(--anim-dur) var(--anim-ease) both}
|
||||
|
||||
/* ---------- RISE / DROP / ZOOM / BLUR / GLITCH ---------- */
|
||||
@keyframes kf-rise{from{opacity:0;transform:translateY(60px) scale(.97);filter:blur(6px)}to{opacity:1;transform:none;filter:none}}
|
||||
@keyframes kf-drop{from{opacity:0;transform:translateY(-60px) scale(.97)}to{opacity:1;transform:none}}
|
||||
@keyframes kf-zoom{0%{opacity:0;transform:scale(.6)}60%{transform:scale(1.04)}100%{opacity:1;transform:scale(1)}}
|
||||
@keyframes kf-blur{from{opacity:0;filter:blur(18px)}to{opacity:1;filter:none}}
|
||||
@keyframes kf-glitch{0%{opacity:0;transform:translateX(0);clip-path:inset(0 0 0 0)}
|
||||
20%{opacity:1;transform:translateX(-6px);clip-path:inset(20% 0 30% 0)}
|
||||
40%{transform:translateX(4px);clip-path:inset(50% 0 10% 0)}
|
||||
60%{transform:translateX(-3px);clip-path:inset(10% 0 60% 0)}
|
||||
80%{transform:translateX(2px);clip-path:inset(0 0 0 0)}
|
||||
100%{opacity:1;transform:none}}
|
||||
.anim-rise-in{animation:kf-rise .9s var(--anim-ease) both}
|
||||
.anim-drop-in{animation:kf-drop .8s var(--anim-ease) both}
|
||||
.anim-zoom-pop{animation:kf-zoom .7s cubic-bezier(.22,1.3,.36,1) both}
|
||||
.anim-blur-in{animation:kf-blur .8s var(--anim-ease) both}
|
||||
.anim-glitch-in{animation:kf-glitch .8s steps(5,end) both}
|
||||
|
||||
/* ---------- TYPEWRITER ---------- */
|
||||
.anim-typewriter{display:inline-block;overflow:hidden;white-space:nowrap;border-right:2px solid currentColor;
|
||||
width:0;animation:kf-type 2.4s steps(40,end) forwards, kf-caret 1s step-end infinite}
|
||||
@keyframes kf-type{to{width:100%}}
|
||||
@keyframes kf-caret{50%{border-color:transparent}}
|
||||
|
||||
/* ---------- GLOW / SHIMMER / GRADIENT-FLOW ---------- */
|
||||
@keyframes kf-neon{0%,100%{text-shadow:0 0 8px var(--accent),0 0 20px var(--accent)}
|
||||
50%{text-shadow:0 0 16px var(--accent),0 0 40px var(--accent),0 0 80px var(--accent)}}
|
||||
.anim-neon-glow{animation:kf-neon 2s ease-in-out infinite}
|
||||
|
||||
.anim-shimmer-sweep{position:relative;overflow:hidden}
|
||||
.anim-shimmer-sweep::after{content:"";position:absolute;inset:0;
|
||||
background:linear-gradient(110deg,transparent 40%,rgba(255,255,255,.55) 50%,transparent 60%);
|
||||
transform:translateX(-100%);animation:kf-shimmer 2.4s var(--anim-ease) infinite}
|
||||
@keyframes kf-shimmer{to{transform:translateX(100%)}}
|
||||
|
||||
.anim-gradient-flow{background:linear-gradient(90deg,var(--accent),var(--accent-2,var(--accent)),var(--accent-3,var(--accent)),var(--accent));
|
||||
background-size:300% 100%;-webkit-background-clip:text;background-clip:text;color:transparent;-webkit-text-fill-color:transparent;
|
||||
animation:kf-gradflow 4s linear infinite}
|
||||
@keyframes kf-gradflow{to{background-position:300% 0}}
|
||||
|
||||
/* ---------- STAGGER LIST ---------- */
|
||||
.anim-stagger-list > *{opacity:0;animation:kf-rise .65s var(--anim-ease) both}
|
||||
.anim-stagger-list > *:nth-child(1){animation-delay:.05s}
|
||||
.anim-stagger-list > *:nth-child(2){animation-delay:.15s}
|
||||
.anim-stagger-list > *:nth-child(3){animation-delay:.25s}
|
||||
.anim-stagger-list > *:nth-child(4){animation-delay:.35s}
|
||||
.anim-stagger-list > *:nth-child(5){animation-delay:.45s}
|
||||
.anim-stagger-list > *:nth-child(6){animation-delay:.55s}
|
||||
.anim-stagger-list > *:nth-child(7){animation-delay:.65s}
|
||||
.anim-stagger-list > *:nth-child(8){animation-delay:.75s}
|
||||
.anim-stagger-list > *:nth-child(n+9){animation-delay:.85s}
|
||||
|
||||
/* ---------- COUNTER-UP (JS-driven, marker class only) ---------- */
|
||||
.counter{font-variant-numeric:tabular-nums}
|
||||
|
||||
/* ---------- SVG PATH DRAW ---------- */
|
||||
.anim-path-draw path,.anim-path-draw line,.anim-path-draw polyline,.anim-path-draw circle,.anim-path-draw rect{
|
||||
stroke-dasharray:1000;stroke-dashoffset:1000;animation:kf-draw 2s var(--anim-ease) forwards}
|
||||
@keyframes kf-draw{to{stroke-dashoffset:0}}
|
||||
|
||||
/* ---------- PARALLAX TILT (hover) ---------- */
|
||||
.anim-parallax-tilt{transform-style:preserve-3d;transition:transform .4s var(--anim-ease)}
|
||||
.anim-parallax-tilt:hover{transform:perspective(900px) rotateX(6deg) rotateY(-8deg) translateZ(10px)}
|
||||
|
||||
/* ---------- CARD FLIP 3D ---------- */
|
||||
@keyframes kf-flip{from{transform:perspective(1200px) rotateY(-90deg);opacity:0}
|
||||
to{transform:perspective(1200px) rotateY(0);opacity:1}}
|
||||
.anim-card-flip-3d{animation:kf-flip .9s var(--anim-ease) both;transform-style:preserve-3d;backface-visibility:hidden}
|
||||
|
||||
/* ---------- CUBE ROTATE 3D ---------- */
|
||||
@keyframes kf-cube{from{transform:perspective(1200px) rotateX(20deg) rotateY(-90deg) translateZ(-200px);opacity:0}
|
||||
to{transform:perspective(1200px) rotateX(0) rotateY(0) translateZ(0);opacity:1}}
|
||||
.anim-cube-rotate-3d{animation:kf-cube 1s var(--anim-ease) both}
|
||||
|
||||
/* ---------- PAGE TURN 3D ---------- */
|
||||
@keyframes kf-pageturn{from{transform:perspective(1600px) rotateY(-85deg);transform-origin:left center;opacity:0}
|
||||
to{transform:perspective(1600px) rotateY(0);opacity:1}}
|
||||
.anim-page-turn-3d{animation:kf-pageturn 1s var(--anim-ease) both;transform-origin:left center}
|
||||
|
||||
/* ---------- PERSPECTIVE ZOOM ---------- */
|
||||
@keyframes kf-pzoom{from{opacity:0;transform:perspective(1400px) translateZ(-400px) rotateX(12deg)}
|
||||
to{opacity:1;transform:none}}
|
||||
.anim-perspective-zoom{animation:kf-pzoom 1s var(--anim-ease) both}
|
||||
|
||||
/* ---------- MARQUEE SCROLL ---------- */
|
||||
.anim-marquee-scroll{display:flex;gap:48px;white-space:nowrap;animation:kf-marquee 20s linear infinite}
|
||||
@keyframes kf-marquee{from{transform:translateX(0)}to{transform:translateX(-50%)}}
|
||||
|
||||
/* ---------- KEN BURNS ---------- */
|
||||
@keyframes kf-kenburns{0%{transform:scale(1) translate(0,0)}100%{transform:scale(1.15) translate(-2%,-1%)}}
|
||||
.anim-kenburns{animation:kf-kenburns 14s ease-in-out infinite alternate}
|
||||
|
||||
/* ---------- CONFETTI BURST (pseudo — pure CSS sparkles) ---------- */
|
||||
.anim-confetti-burst{position:relative}
|
||||
.anim-confetti-burst::before,.anim-confetti-burst::after{
|
||||
content:"";position:absolute;top:50%;left:50%;width:8px;height:8px;border-radius:50%;
|
||||
background:var(--accent);box-shadow:
|
||||
20px -30px 0 var(--accent-2,var(--accent)),-25px -20px 0 var(--accent-3,var(--accent)),
|
||||
30px 20px 0 var(--good,#1aaf6c),-30px 25px 0 var(--warn,#f5a524),
|
||||
40px -10px 0 var(--bad,#e0445a),-45px 0 0 var(--accent),
|
||||
10px 40px 0 var(--accent-2,var(--accent)),-15px -40px 0 var(--accent-3,var(--accent));
|
||||
opacity:0;animation:kf-confetti 1.2s var(--anim-ease) forwards}
|
||||
.anim-confetti-burst::after{animation-delay:.15s;transform:rotate(45deg)}
|
||||
@keyframes kf-confetti{0%{opacity:0;transform:scale(.2)}30%{opacity:1}100%{opacity:0;transform:scale(2.2)}}
|
||||
|
||||
/* ---------- SPOTLIGHT ---------- */
|
||||
@keyframes kf-spot{0%{clip-path:circle(0% at 50% 50%)}100%{clip-path:circle(140% at 50% 50%)}}
|
||||
.anim-spotlight{animation:kf-spot 1.1s var(--anim-ease) both}
|
||||
|
||||
/* ---------- MORPH SHAPE (SVG) ---------- */
|
||||
.anim-morph-shape path{animation:kf-morph 6s ease-in-out infinite alternate}
|
||||
@keyframes kf-morph{0%{d:path("M60,120 Q120,20 180,120 T300,120")}
|
||||
100%{d:path("M60,120 Q120,220 180,120 T300,120")}}
|
||||
|
||||
/* ---------- RIPPLE REVEAL ---------- */
|
||||
@keyframes kf-ripple{0%{clip-path:circle(0% at 20% 80%);opacity:.4}
|
||||
100%{clip-path:circle(160% at 20% 80%);opacity:1}}
|
||||
.anim-ripple-reveal{animation:kf-ripple 1.2s var(--anim-ease) both}
|
||||
|
||||
/* reduced motion */
|
||||
@media (prefers-reduced-motion: reduce){
|
||||
[class*="anim-"]{animation:none!important;transition:none!important}
|
||||
}
|
||||
99
skills/html-ppt/assets/animations/fx-runtime.js
Normal file
99
skills/html-ppt/assets/animations/fx-runtime.js
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
/* html-ppt :: fx-runtime.js
|
||||
* Canvas FX autoloader + lifecycle manager.
|
||||
* - Dynamically loads all fx modules listed in FX_LIST
|
||||
* - Initializes [data-fx] elements when their slide becomes active
|
||||
* - Calls handle.stop() when the slide leaves
|
||||
*/
|
||||
(function(){
|
||||
'use strict';
|
||||
|
||||
const FX_LIST = [
|
||||
'_util',
|
||||
'particle-burst','confetti-cannon','firework','starfield','matrix-rain',
|
||||
'knowledge-graph','neural-net','constellation','orbit-ring','galaxy-swirl',
|
||||
'word-cascade','letter-explode','chain-react','magnetic-field','data-stream',
|
||||
'gradient-blob','sparkle-trail','shockwave','typewriter-multi','counter-explosion'
|
||||
];
|
||||
|
||||
// Resolve base path of this script so it works from any page location.
|
||||
const myScript = document.currentScript || (function(){
|
||||
const all = document.getElementsByTagName('script');
|
||||
for (const s of all){ if (s.src && s.src.indexOf('fx-runtime.js')>-1) return s; }
|
||||
return null;
|
||||
})();
|
||||
const base = myScript ? myScript.src.replace(/fx-runtime\.js.*$/, 'fx/') : 'assets/animations/fx/';
|
||||
|
||||
let loaded = 0;
|
||||
const total = FX_LIST.length;
|
||||
const ready = new Promise((resolve) => {
|
||||
if (!total) return resolve();
|
||||
FX_LIST.forEach((name) => {
|
||||
const s = document.createElement('script');
|
||||
s.src = base + name + '.js';
|
||||
s.async = false;
|
||||
s.onload = s.onerror = () => { if (++loaded >= total) resolve(); };
|
||||
document.head.appendChild(s);
|
||||
});
|
||||
});
|
||||
|
||||
window.__hpxActive = window.__hpxActive || new Map();
|
||||
|
||||
function initFxIn(root){
|
||||
if (!window.HPX) return;
|
||||
const els = root.querySelectorAll('[data-fx]');
|
||||
els.forEach((el) => {
|
||||
if (window.__hpxActive.has(el)) return;
|
||||
const name = el.getAttribute('data-fx');
|
||||
const fn = window.HPX[name];
|
||||
if (typeof fn !== 'function') return;
|
||||
try {
|
||||
const handle = fn(el, {}) || { stop(){} };
|
||||
window.__hpxActive.set(el, handle);
|
||||
} catch(e){ console.warn('[hpx-fx]', name, e); }
|
||||
});
|
||||
}
|
||||
|
||||
function stopFxIn(root){
|
||||
const els = root.querySelectorAll('[data-fx]');
|
||||
els.forEach((el) => {
|
||||
const h = window.__hpxActive.get(el);
|
||||
if (h && typeof h.stop === 'function'){
|
||||
try{ h.stop(); }catch(e){}
|
||||
}
|
||||
window.__hpxActive.delete(el);
|
||||
});
|
||||
}
|
||||
|
||||
function reinitFxIn(root){
|
||||
stopFxIn(root);
|
||||
initFxIn(root);
|
||||
}
|
||||
window.__hpxReinit = reinitFxIn;
|
||||
|
||||
function boot(){
|
||||
ready.then(() => {
|
||||
const active = document.querySelector('.slide.is-active') || document.querySelector('.slide');
|
||||
if (active) initFxIn(active);
|
||||
|
||||
// Watch all slides for class changes
|
||||
const slides = document.querySelectorAll('.slide');
|
||||
slides.forEach((sl) => {
|
||||
const mo = new MutationObserver((muts) => {
|
||||
for (const m of muts){
|
||||
if (m.attributeName === 'class'){
|
||||
if (sl.classList.contains('is-active')) initFxIn(sl);
|
||||
else stopFxIn(sl);
|
||||
}
|
||||
}
|
||||
});
|
||||
mo.observe(sl, { attributes: true, attributeFilter: ['class'] });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading'){
|
||||
document.addEventListener('DOMContentLoaded', boot);
|
||||
} else {
|
||||
boot();
|
||||
}
|
||||
})();
|
||||
63
skills/html-ppt/assets/animations/fx/_util.js
Normal file
63
skills/html-ppt/assets/animations/fx/_util.js
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
/* html-ppt fx :: shared helpers */
|
||||
(function(){
|
||||
window.HPX = window.HPX || {};
|
||||
const U = window.HPX._u = {};
|
||||
|
||||
U.css = (el, name, fb) => {
|
||||
const v = getComputedStyle(el).getPropertyValue(name).trim();
|
||||
return v || fb;
|
||||
};
|
||||
|
||||
U.accent = (el, fb) => U.css(el, '--accent', fb || '#7c5cff');
|
||||
U.accent2 = (el, fb) => U.css(el, '--accent-2', fb || '#22d3ee');
|
||||
U.text = (el, fb) => U.css(el, '--text-1', fb || '#eaeaf2');
|
||||
|
||||
U.palette = (el) => [
|
||||
U.accent(el, '#7c5cff'),
|
||||
U.accent2(el, '#22d3ee'),
|
||||
U.css(el, '--ok', '#22c55e'),
|
||||
U.css(el, '--warn', '#f59e0b'),
|
||||
U.css(el, '--danger', '#ef4444'),
|
||||
];
|
||||
|
||||
U.canvas = (el) => {
|
||||
if (getComputedStyle(el).position === 'static') el.style.position = 'relative';
|
||||
const c = document.createElement('canvas');
|
||||
c.style.cssText = 'position:absolute;inset:0;width:100%;height:100%;pointer-events:none;display:block;';
|
||||
el.appendChild(c);
|
||||
const ctx = c.getContext('2d');
|
||||
let w = 0, h = 0, dpr = Math.max(1, Math.min(2, window.devicePixelRatio||1));
|
||||
const fit = () => {
|
||||
const r = el.getBoundingClientRect();
|
||||
w = Math.max(1, r.width|0);
|
||||
h = Math.max(1, r.height|0);
|
||||
c.width = (w*dpr)|0;
|
||||
c.height = (h*dpr)|0;
|
||||
ctx.setTransform(dpr,0,0,dpr,0,0);
|
||||
};
|
||||
fit();
|
||||
const ro = new ResizeObserver(fit);
|
||||
ro.observe(el);
|
||||
return {
|
||||
c, ctx,
|
||||
get w(){return w;}, get h(){return h;}, get dpr(){return dpr;},
|
||||
destroy(){
|
||||
try{ro.disconnect();}catch(e){}
|
||||
if (c.parentNode) c.parentNode.removeChild(c);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
U.loop = (fn) => {
|
||||
let raf = 0, stopped = false, t0 = performance.now();
|
||||
const tick = (t) => {
|
||||
if (stopped) return;
|
||||
fn((t - t0)/1000);
|
||||
raf = requestAnimationFrame(tick);
|
||||
};
|
||||
raf = requestAnimationFrame(tick);
|
||||
return () => { stopped = true; cancelAnimationFrame(raf); };
|
||||
};
|
||||
|
||||
U.rand = (a,b) => a + Math.random()*(b-a);
|
||||
})();
|
||||
41
skills/html-ppt/assets/animations/fx/chain-react.js
Normal file
41
skills/html-ppt/assets/animations/fx/chain-react.js
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
(function(){
|
||||
window.HPX = window.HPX || {};
|
||||
window.HPX['chain-react'] = function(el){
|
||||
const U = window.HPX._u;
|
||||
const k = U.canvas(el), ctx = k.ctx;
|
||||
const ac = U.accent(el,'#7c5cff'), ac2 = U.accent2(el,'#22d3ee');
|
||||
const N = 8;
|
||||
const stop = U.loop((t) => {
|
||||
ctx.clearRect(0,0,k.w,k.h);
|
||||
const cy = k.h/2;
|
||||
const pad = 60;
|
||||
const dx = (k.w - pad*2)/(N-1);
|
||||
const period = 2.4;
|
||||
const phase = (t % period) / period; // 0..1
|
||||
for (let i=0;i<N;i++){
|
||||
const x = pad + i*dx;
|
||||
const my = i/(N-1);
|
||||
const d = Math.abs(phase - my);
|
||||
const pulse = Math.max(0, 1 - d*6);
|
||||
const r = 18 + pulse*18;
|
||||
// glow
|
||||
const g = ctx.createRadialGradient(x,cy,0,x,cy,r*2);
|
||||
g.addColorStop(0, `rgba(124,92,255,${0.4*pulse})`);
|
||||
g.addColorStop(1, 'rgba(0,0,0,0)');
|
||||
ctx.fillStyle = g;
|
||||
ctx.fillRect(x-r*2, cy-r*2, r*4, r*4);
|
||||
// circle
|
||||
ctx.fillStyle = pulse>0.1 ? ac2 : ac;
|
||||
ctx.beginPath(); ctx.arc(x,cy,r,0,Math.PI*2); ctx.fill();
|
||||
ctx.strokeStyle='rgba(255,255,255,0.4)'; ctx.lineWidth=2;
|
||||
ctx.stroke();
|
||||
// connectors
|
||||
if (i<N-1){
|
||||
ctx.strokeStyle='rgba(200,200,230,0.3)'; ctx.lineWidth=2;
|
||||
ctx.beginPath(); ctx.moveTo(x+r,cy); ctx.lineTo(x+dx-r,cy); ctx.stroke();
|
||||
}
|
||||
}
|
||||
});
|
||||
return { stop(){ stop(); k.destroy(); } };
|
||||
};
|
||||
})();
|
||||
49
skills/html-ppt/assets/animations/fx/confetti-cannon.js
Normal file
49
skills/html-ppt/assets/animations/fx/confetti-cannon.js
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
(function(){
|
||||
window.HPX = window.HPX || {};
|
||||
window.HPX['confetti-cannon'] = function(el){
|
||||
const U = window.HPX._u;
|
||||
const k = U.canvas(el), ctx = k.ctx;
|
||||
const pal = U.palette(el);
|
||||
let parts = [];
|
||||
const fire = () => {
|
||||
for (let side=0; side<2; side++){
|
||||
const x0 = side===0 ? 20 : k.w-20;
|
||||
const y0 = k.h - 20;
|
||||
for (let i=0;i<40;i++){
|
||||
const a = side===0 ? U.rand(-Math.PI*0.7, -Math.PI*0.4) : U.rand(-Math.PI*0.6, -Math.PI*0.3) - Math.PI/2 - Math.PI/6;
|
||||
const spd = U.rand(300, 520);
|
||||
parts.push({
|
||||
x: x0, y: y0,
|
||||
vx: Math.cos(a)*spd, vy: Math.sin(a)*spd,
|
||||
w: U.rand(6,12), h: U.rand(3,7),
|
||||
rot: Math.random()*Math.PI, vr: U.rand(-6,6),
|
||||
c: pal[(Math.random()*pal.length)|0],
|
||||
life: 1
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
fire();
|
||||
let last = 0;
|
||||
const stop = U.loop((t) => {
|
||||
ctx.clearRect(0,0,k.w,k.h);
|
||||
if (t - last > 3) { fire(); last = t; }
|
||||
const dt = 1/60;
|
||||
parts = parts.filter(p => p.life > 0 && p.y < k.h+40);
|
||||
for (const p of parts){
|
||||
p.vy += 520*dt;
|
||||
p.x += p.vx*dt; p.y += p.vy*dt;
|
||||
p.rot += p.vr*dt;
|
||||
p.life -= 0.006;
|
||||
ctx.save();
|
||||
ctx.translate(p.x, p.y); ctx.rotate(p.rot);
|
||||
ctx.globalAlpha = Math.max(0, p.life);
|
||||
ctx.fillStyle = p.c;
|
||||
ctx.fillRect(-p.w/2, -p.h/2, p.w, p.h);
|
||||
ctx.restore();
|
||||
}
|
||||
ctx.globalAlpha = 1;
|
||||
});
|
||||
return { stop(){ stop(); k.destroy(); } };
|
||||
};
|
||||
})();
|
||||
44
skills/html-ppt/assets/animations/fx/constellation.js
Normal file
44
skills/html-ppt/assets/animations/fx/constellation.js
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
(function(){
|
||||
window.HPX = window.HPX || {};
|
||||
window.HPX['constellation'] = function(el){
|
||||
const U = window.HPX._u;
|
||||
const k = U.canvas(el), ctx = k.ctx;
|
||||
const ac = U.accent(el,'#9fb4ff');
|
||||
const N = 70;
|
||||
let pts = [];
|
||||
const seed = () => {
|
||||
pts = Array.from({length:N}, () => ({
|
||||
x: Math.random()*k.w, y: Math.random()*k.h,
|
||||
vx: U.rand(-0.3,0.3), vy: U.rand(-0.3,0.3)
|
||||
}));
|
||||
};
|
||||
seed();
|
||||
let lw=k.w, lh=k.h;
|
||||
const stop = U.loop(() => {
|
||||
if (k.w!==lw||k.h!==lh){ seed(); lw=k.w; lh=k.h; }
|
||||
ctx.clearRect(0,0,k.w,k.h);
|
||||
for (const p of pts){
|
||||
p.x += p.vx; p.y += p.vy;
|
||||
if (p.x<0||p.x>k.w) p.vx*=-1;
|
||||
if (p.y<0||p.y>k.h) p.vy*=-1;
|
||||
}
|
||||
for (let i=0;i<N;i++){
|
||||
for (let j=i+1;j<N;j++){
|
||||
const a=pts[i], b=pts[j];
|
||||
const d = Math.hypot(a.x-b.x, a.y-b.y);
|
||||
if (d < 150){
|
||||
ctx.globalAlpha = 1 - d/150;
|
||||
ctx.strokeStyle = ac; ctx.lineWidth=1;
|
||||
ctx.beginPath(); ctx.moveTo(a.x,a.y); ctx.lineTo(b.x,b.y); ctx.stroke();
|
||||
}
|
||||
}
|
||||
}
|
||||
ctx.globalAlpha = 1;
|
||||
ctx.fillStyle = ac;
|
||||
for (const p of pts){
|
||||
ctx.beginPath(); ctx.arc(p.x,p.y,1.8,0,Math.PI*2); ctx.fill();
|
||||
}
|
||||
});
|
||||
return { stop(){ stop(); k.destroy(); } };
|
||||
};
|
||||
})();
|
||||
58
skills/html-ppt/assets/animations/fx/counter-explosion.js
Normal file
58
skills/html-ppt/assets/animations/fx/counter-explosion.js
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
(function(){
|
||||
window.HPX = window.HPX || {};
|
||||
window.HPX['counter-explosion'] = function(el){
|
||||
const U = window.HPX._u;
|
||||
if (getComputedStyle(el).position === 'static') el.style.position = 'relative';
|
||||
const target = parseInt(el.getAttribute('data-fx-to') || '2400', 10);
|
||||
const k = U.canvas(el), ctx = k.ctx;
|
||||
const pal = U.palette(el);
|
||||
// number overlay
|
||||
const num = document.createElement('div');
|
||||
num.style.cssText = 'position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font:900 120px system-ui,sans-serif;color:var(--text-1,#fff);pointer-events:none;text-shadow:0 4px 40px rgba(124,92,255,0.5);';
|
||||
num.textContent = '0';
|
||||
el.appendChild(num);
|
||||
let parts = [];
|
||||
let state = 'count'; // count | burst | hold
|
||||
let stateT = 0;
|
||||
let value = 0;
|
||||
let cycle = 0;
|
||||
const burst = () => {
|
||||
const cx = k.w/2, cy = k.h/2;
|
||||
for (let i=0;i<120;i++){
|
||||
const a = Math.random()*Math.PI*2;
|
||||
const s = U.rand(120, 400);
|
||||
parts.push({x:cx,y:cy,vx:Math.cos(a)*s,vy:Math.sin(a)*s,life:1,r:U.rand(2,5),c:pal[(Math.random()*pal.length)|0]});
|
||||
}
|
||||
};
|
||||
const stop = U.loop(() => {
|
||||
ctx.clearRect(0,0,k.w,k.h);
|
||||
const dt = 1/60;
|
||||
stateT += dt;
|
||||
if (state === 'count'){
|
||||
const dur = 2.2;
|
||||
const p = Math.min(1, stateT/dur);
|
||||
const eased = 1 - Math.pow(1-p,3);
|
||||
value = Math.round(target*eased);
|
||||
num.textContent = value.toLocaleString();
|
||||
if (p >= 1){ state='burst'; stateT=0; burst(); }
|
||||
} else if (state === 'burst'){
|
||||
if (stateT > 0.05 && stateT < 0.3 && parts.length < 200) {}
|
||||
if (stateT > 2.5){ state='hold'; stateT=0; }
|
||||
} else if (state === 'hold'){
|
||||
if (stateT > 1.5){
|
||||
state='count'; stateT=0; value=0; num.textContent='0'; cycle++;
|
||||
}
|
||||
}
|
||||
parts = parts.filter(p => p.life > 0);
|
||||
for (const p of parts){
|
||||
p.vy += 260*dt; p.vx *= 0.985; p.vy *= 0.985;
|
||||
p.x += p.vx*dt; p.y += p.vy*dt; p.life -= 0.01;
|
||||
ctx.globalAlpha = Math.max(0,p.life);
|
||||
ctx.fillStyle = p.c;
|
||||
ctx.beginPath(); ctx.arc(p.x,p.y,p.r,0,Math.PI*2); ctx.fill();
|
||||
}
|
||||
ctx.globalAlpha = 1;
|
||||
});
|
||||
return { stop(){ stop(); k.destroy(); if (num.parentNode) num.parentNode.removeChild(num); } };
|
||||
};
|
||||
})();
|
||||
45
skills/html-ppt/assets/animations/fx/data-stream.js
Normal file
45
skills/html-ppt/assets/animations/fx/data-stream.js
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
(function(){
|
||||
window.HPX = window.HPX || {};
|
||||
window.HPX['data-stream'] = function(el){
|
||||
const U = window.HPX._u;
|
||||
const k = U.canvas(el), ctx = k.ctx;
|
||||
const ac = U.accent(el,'#22d3ee'), ac2 = U.accent2(el,'#7c5cff');
|
||||
const rows = [];
|
||||
const rh = 22;
|
||||
const genRow = (y) => ({
|
||||
y, dir: Math.random()<0.5?-1:1,
|
||||
speed: U.rand(30, 90),
|
||||
offset: Math.random()*2000,
|
||||
text: Array.from({length:120}, () => {
|
||||
const r = Math.random();
|
||||
if (r<0.3) return Math.random()<0.5?'0':'1';
|
||||
if (r<0.6) return '0x' + Math.floor(Math.random()*256).toString(16).padStart(2,'0');
|
||||
return Math.random().toString(16).slice(2,6);
|
||||
}).join(' ')
|
||||
});
|
||||
const init = () => {
|
||||
rows.length = 0;
|
||||
const n = Math.ceil(k.h/rh);
|
||||
for (let i=0;i<n;i++) rows.push(genRow(i*rh + rh*0.7));
|
||||
};
|
||||
init();
|
||||
let lh = k.h;
|
||||
const stop = U.loop((t) => {
|
||||
if (k.h!==lh){ init(); lh=k.h; }
|
||||
ctx.fillStyle = 'rgba(5,8,14,0.35)';
|
||||
ctx.fillRect(0,0,k.w,k.h);
|
||||
ctx.font = '13px ui-monospace,Menlo,monospace';
|
||||
for (let i=0;i<rows.length;i++){
|
||||
const r = rows[i];
|
||||
const x = r.dir>0
|
||||
? ((t*r.speed + r.offset) % (k.w+400)) - 400
|
||||
: k.w - (((t*r.speed + r.offset) % (k.w+400)) - 400);
|
||||
ctx.fillStyle = (i%3===0)?ac:ac2;
|
||||
ctx.globalAlpha = 0.65 + (i%2)*0.3;
|
||||
ctx.fillText(r.text, x, r.y);
|
||||
}
|
||||
ctx.globalAlpha = 1;
|
||||
});
|
||||
return { stop(){ stop(); k.destroy(); } };
|
||||
};
|
||||
})();
|
||||
51
skills/html-ppt/assets/animations/fx/firework.js
Normal file
51
skills/html-ppt/assets/animations/fx/firework.js
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
(function(){
|
||||
window.HPX = window.HPX || {};
|
||||
window.HPX['firework'] = function(el){
|
||||
const U = window.HPX._u;
|
||||
const k = U.canvas(el), ctx = k.ctx;
|
||||
const pal = U.palette(el);
|
||||
let rockets = [], sparks = [];
|
||||
const launch = () => {
|
||||
rockets.push({
|
||||
x: U.rand(k.w*0.2, k.w*0.8), y: k.h+10,
|
||||
vx: U.rand(-30,30), vy: U.rand(-520,-380),
|
||||
tgtY: U.rand(k.h*0.15, k.h*0.45),
|
||||
c: pal[(Math.random()*pal.length)|0]
|
||||
});
|
||||
};
|
||||
const burst = (x, y, c) => {
|
||||
const n = 70;
|
||||
for (let i=0;i<n;i++){
|
||||
const a = Math.random()*Math.PI*2;
|
||||
const s = U.rand(60, 240);
|
||||
sparks.push({x,y,vx:Math.cos(a)*s,vy:Math.sin(a)*s,life:1,c});
|
||||
}
|
||||
};
|
||||
let last = -1;
|
||||
const stop = U.loop((t) => {
|
||||
ctx.fillStyle = 'rgba(0,0,0,0.18)';
|
||||
ctx.fillRect(0,0,k.w,k.h);
|
||||
if (t - last > 0.7) { launch(); last = t; }
|
||||
const dt = 1/60;
|
||||
rockets = rockets.filter(r => {
|
||||
r.x += r.vx*dt; r.y += r.vy*dt; r.vy += 260*dt;
|
||||
ctx.fillStyle = r.c;
|
||||
ctx.beginPath(); ctx.arc(r.x, r.y, 2.5, 0, Math.PI*2); ctx.fill();
|
||||
if (r.y <= r.tgtY || r.vy >= 0) { burst(r.x, r.y, r.c); return false; }
|
||||
return true;
|
||||
});
|
||||
sparks = sparks.filter(p => p.life > 0);
|
||||
for (const p of sparks){
|
||||
p.vy += 90*dt;
|
||||
p.vx *= 0.98; p.vy *= 0.98;
|
||||
p.x += p.vx*dt; p.y += p.vy*dt;
|
||||
p.life -= 0.012;
|
||||
ctx.globalAlpha = Math.max(0, p.life);
|
||||
ctx.fillStyle = p.c;
|
||||
ctx.beginPath(); ctx.arc(p.x, p.y, 2, 0, Math.PI*2); ctx.fill();
|
||||
}
|
||||
ctx.globalAlpha = 1;
|
||||
});
|
||||
return { stop(){ stop(); k.destroy(); } };
|
||||
};
|
||||
})();
|
||||
33
skills/html-ppt/assets/animations/fx/galaxy-swirl.js
Normal file
33
skills/html-ppt/assets/animations/fx/galaxy-swirl.js
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
(function(){
|
||||
window.HPX = window.HPX || {};
|
||||
window.HPX['galaxy-swirl'] = function(el){
|
||||
const U = window.HPX._u;
|
||||
const k = U.canvas(el), ctx = k.ctx;
|
||||
const pal = U.palette(el);
|
||||
const N = 800;
|
||||
const parts = Array.from({length:N}, (_,i) => {
|
||||
const arm = i%3;
|
||||
const t = Math.random();
|
||||
const r = t*180 + 8;
|
||||
const base = (arm/3)*Math.PI*2;
|
||||
return { r, a: base + Math.log(r+1)*1.6 + U.rand(-0.2,0.2),
|
||||
c: pal[arm%pal.length],
|
||||
s: U.rand(0.8, 2.2) };
|
||||
});
|
||||
const stop = U.loop((t) => {
|
||||
ctx.fillStyle = 'rgba(0,0,0,0.15)';
|
||||
ctx.fillRect(0,0,k.w,k.h);
|
||||
const cx=k.w/2, cy=k.h/2;
|
||||
for (const p of parts){
|
||||
const a = p.a + t*0.15;
|
||||
const x = cx + Math.cos(a)*p.r;
|
||||
const y = cy + Math.sin(a)*p.r*0.7;
|
||||
ctx.fillStyle = p.c;
|
||||
ctx.globalAlpha = 0.7;
|
||||
ctx.beginPath(); ctx.arc(x,y,p.s,0,Math.PI*2); ctx.fill();
|
||||
}
|
||||
ctx.globalAlpha = 1;
|
||||
});
|
||||
return { stop(){ stop(); k.destroy(); } };
|
||||
};
|
||||
})();
|
||||
39
skills/html-ppt/assets/animations/fx/gradient-blob.js
Normal file
39
skills/html-ppt/assets/animations/fx/gradient-blob.js
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
(function(){
|
||||
window.HPX = window.HPX || {};
|
||||
window.HPX['gradient-blob'] = function(el){
|
||||
const U = window.HPX._u;
|
||||
const k = U.canvas(el), ctx = k.ctx;
|
||||
const pal = U.palette(el);
|
||||
const blobs = Array.from({length:4}, (_,i) => ({
|
||||
x: U.rand(0,1), y: U.rand(0,1),
|
||||
vx: U.rand(-0.08,0.08), vy: U.rand(-0.08,0.08),
|
||||
r: U.rand(180,320),
|
||||
c: pal[i%pal.length]
|
||||
}));
|
||||
const hex2rgb = (h) => {
|
||||
const m = h.replace('#','').match(/.{2}/g);
|
||||
if (!m) return [124,92,255];
|
||||
return m.map(x=>parseInt(x,16));
|
||||
};
|
||||
const stop = U.loop((t) => {
|
||||
ctx.fillStyle = 'rgba(10,12,22,0.2)';
|
||||
ctx.fillRect(0,0,k.w,k.h);
|
||||
ctx.globalCompositeOperation = 'lighter';
|
||||
for (const b of blobs){
|
||||
b.x += b.vx*0.01; b.y += b.vy*0.01;
|
||||
if (b.x<0||b.x>1) b.vx*=-1;
|
||||
if (b.y<0||b.y>1) b.vy*=-1;
|
||||
const px = b.x*k.w, py = b.y*k.h;
|
||||
const r = b.r + Math.sin(t*0.8 + b.x*6)*30;
|
||||
const [R,G,B] = hex2rgb(b.c);
|
||||
const grad = ctx.createRadialGradient(px,py,0,px,py,r);
|
||||
grad.addColorStop(0, `rgba(${R},${G},${B},0.55)`);
|
||||
grad.addColorStop(1, `rgba(${R},${G},${B},0)`);
|
||||
ctx.fillStyle = grad;
|
||||
ctx.beginPath(); ctx.arc(px,py,r,0,Math.PI*2); ctx.fill();
|
||||
}
|
||||
ctx.globalCompositeOperation = 'source-over';
|
||||
});
|
||||
return { stop(){ stop(); k.destroy(); } };
|
||||
};
|
||||
})();
|
||||
69
skills/html-ppt/assets/animations/fx/knowledge-graph.js
Normal file
69
skills/html-ppt/assets/animations/fx/knowledge-graph.js
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
(function(){
|
||||
window.HPX = window.HPX || {};
|
||||
window.HPX['knowledge-graph'] = function(el){
|
||||
const U = window.HPX._u;
|
||||
const k = U.canvas(el), ctx = k.ctx;
|
||||
const pal = U.palette(el);
|
||||
const tx = U.text(el, '#e7e7ef');
|
||||
const labels = ['AI','ML','LLM','Graph','Node','Edge','Claude','GPT','RAG','Vector',
|
||||
'Embed','Neural','Agent','Tool','Memory','Logic','Data','Train','Infer','Token',
|
||||
'Prompt','Chain','Plan','Skill','Cloud','Edge','GPU','Code','Task','Flow'];
|
||||
const N = 28;
|
||||
const nodes = Array.from({length:N}, (_,i) => ({
|
||||
x: U.rand(40, 300), y: U.rand(40, 200),
|
||||
vx: 0, vy: 0, label: labels[i%labels.length],
|
||||
c: pal[i%pal.length]
|
||||
}));
|
||||
const edges = [];
|
||||
const made = new Set();
|
||||
while (edges.length < 50){
|
||||
const a = (Math.random()*N)|0, b = (Math.random()*N)|0;
|
||||
if (a===b) continue;
|
||||
const key = a<b ? a+'-'+b : b+'-'+a;
|
||||
if (made.has(key)) continue;
|
||||
made.add(key); edges.push([a,b]);
|
||||
}
|
||||
const stop = U.loop(() => {
|
||||
// physics
|
||||
for (let i=0;i<N;i++){
|
||||
for (let j=i+1;j<N;j++){
|
||||
const a=nodes[i], b=nodes[j];
|
||||
const dx=b.x-a.x, dy=b.y-a.y;
|
||||
let d2=dx*dx+dy*dy; if (d2<1) d2=1;
|
||||
const d=Math.sqrt(d2);
|
||||
const f=1600/d2;
|
||||
const fx=(dx/d)*f, fy=(dy/d)*f;
|
||||
a.vx-=fx; a.vy-=fy; b.vx+=fx; b.vy+=fy;
|
||||
}
|
||||
}
|
||||
for (const [i,j] of edges){
|
||||
const a=nodes[i], b=nodes[j];
|
||||
const dx=b.x-a.x, dy=b.y-a.y, d=Math.hypot(dx,dy)||1;
|
||||
const f=(d-90)*0.008;
|
||||
const fx=(dx/d)*f, fy=(dy/d)*f;
|
||||
a.vx+=fx; a.vy+=fy; b.vx-=fx; b.vy-=fy;
|
||||
}
|
||||
const cx=k.w/2, cy=k.h/2;
|
||||
for (const n of nodes){
|
||||
n.vx += (cx-n.x)*0.002;
|
||||
n.vy += (cy-n.y)*0.002;
|
||||
n.vx *= 0.85; n.vy *= 0.85;
|
||||
n.x += n.vx; n.y += n.vy;
|
||||
}
|
||||
ctx.clearRect(0,0,k.w,k.h);
|
||||
ctx.strokeStyle = 'rgba(180,180,220,0.25)'; ctx.lineWidth=1;
|
||||
for (const [i,j] of edges){
|
||||
const a=nodes[i], b=nodes[j];
|
||||
ctx.beginPath(); ctx.moveTo(a.x,a.y); ctx.lineTo(b.x,b.y); ctx.stroke();
|
||||
}
|
||||
ctx.font='11px system-ui,sans-serif'; ctx.textAlign='center'; ctx.textBaseline='middle';
|
||||
for (const n of nodes){
|
||||
ctx.fillStyle = n.c;
|
||||
ctx.beginPath(); ctx.arc(n.x,n.y,7,0,Math.PI*2); ctx.fill();
|
||||
ctx.fillStyle = tx;
|
||||
ctx.fillText(n.label, n.x, n.y-14);
|
||||
}
|
||||
});
|
||||
return { stop(){ stop(); k.destroy(); } };
|
||||
};
|
||||
})();
|
||||
50
skills/html-ppt/assets/animations/fx/letter-explode.js
Normal file
50
skills/html-ppt/assets/animations/fx/letter-explode.js
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
(function(){
|
||||
window.HPX = window.HPX || {};
|
||||
window.HPX['letter-explode'] = function(el){
|
||||
const U = window.HPX._u;
|
||||
if (getComputedStyle(el).position === 'static') el.style.position = 'relative';
|
||||
const src = el.querySelector('[data-fx-text]') || el;
|
||||
const text = (el.getAttribute('data-fx-text-value') || src.textContent || 'EXPLODE').trim();
|
||||
// Build a container, hide source text
|
||||
const wrap = document.createElement('div');
|
||||
wrap.style.cssText = 'position:absolute;inset:0;display:flex;align-items:center;justify-content:center;pointer-events:none;';
|
||||
const inner = document.createElement('div');
|
||||
inner.style.cssText = 'font-size:64px;font-weight:900;letter-spacing:0.02em;color:var(--text-1,#fff);white-space:nowrap;';
|
||||
wrap.appendChild(inner);
|
||||
el.appendChild(wrap);
|
||||
const spans = [];
|
||||
for (const ch of text){
|
||||
const s = document.createElement('span');
|
||||
s.textContent = ch === ' ' ? '\u00A0' : ch;
|
||||
s.style.display='inline-block';
|
||||
s.style.transform='translate(0,0)';
|
||||
s.style.transition='transform 900ms cubic-bezier(.2,.9,.3,1), opacity 900ms';
|
||||
s.style.opacity='0';
|
||||
inner.appendChild(s);
|
||||
spans.push(s);
|
||||
}
|
||||
let stopped = false;
|
||||
const run = () => {
|
||||
if (stopped) return;
|
||||
spans.forEach((s,i) => {
|
||||
const dx = U.rand(-400, 400), dy = U.rand(-300, 300);
|
||||
s.style.transition='none';
|
||||
s.style.transform=`translate(${dx}px,${dy}px) rotate(${U.rand(-180,180)}deg)`;
|
||||
s.style.opacity='0';
|
||||
});
|
||||
// force reflow
|
||||
void inner.offsetWidth;
|
||||
spans.forEach((s,i) => {
|
||||
setTimeout(() => {
|
||||
if (stopped) return;
|
||||
s.style.transition='transform 900ms cubic-bezier(.2,.9,.3,1), opacity 900ms';
|
||||
s.style.transform='translate(0,0) rotate(0deg)';
|
||||
s.style.opacity='1';
|
||||
}, i*35);
|
||||
});
|
||||
};
|
||||
run();
|
||||
const iv = setInterval(run, 4500);
|
||||
return { stop(){ stopped=true; clearInterval(iv); if (wrap.parentNode) wrap.parentNode.removeChild(wrap); } };
|
||||
};
|
||||
})();
|
||||
40
skills/html-ppt/assets/animations/fx/magnetic-field.js
Normal file
40
skills/html-ppt/assets/animations/fx/magnetic-field.js
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
(function(){
|
||||
window.HPX = window.HPX || {};
|
||||
window.HPX['magnetic-field'] = function(el){
|
||||
const U = window.HPX._u;
|
||||
const k = U.canvas(el), ctx = k.ctx;
|
||||
const pal = U.palette(el);
|
||||
const N = 60;
|
||||
const parts = Array.from({length:N}, (_,i) => ({
|
||||
phase: Math.random()*Math.PI*2,
|
||||
freq: U.rand(0.4, 1.2),
|
||||
amp: U.rand(30, 90),
|
||||
y0: U.rand(0.15, 0.85),
|
||||
c: pal[i%pal.length],
|
||||
trail: []
|
||||
}));
|
||||
const stop = U.loop((t) => {
|
||||
ctx.fillStyle = 'rgba(0,0,0,0.08)';
|
||||
ctx.fillRect(0,0,k.w,k.h);
|
||||
for (const p of parts){
|
||||
const x = ((t*80 + p.phase*50) % (k.w+100)) - 50;
|
||||
const y = k.h*p.y0 + Math.sin(x*0.02 + p.phase + t*p.freq)*p.amp;
|
||||
p.trail.push([x,y]);
|
||||
if (p.trail.length > 18) p.trail.shift();
|
||||
ctx.strokeStyle = p.c;
|
||||
ctx.lineWidth = 2;
|
||||
ctx.beginPath();
|
||||
for (let i=0;i<p.trail.length;i++){
|
||||
const [tx,ty] = p.trail[i];
|
||||
if (i===0) ctx.moveTo(tx,ty); else ctx.lineTo(tx,ty);
|
||||
}
|
||||
ctx.globalAlpha = 0.7;
|
||||
ctx.stroke();
|
||||
ctx.globalAlpha = 1;
|
||||
ctx.fillStyle = p.c;
|
||||
ctx.beginPath(); ctx.arc(x,y,2.5,0,Math.PI*2); ctx.fill();
|
||||
}
|
||||
});
|
||||
return { stop(){ stop(); k.destroy(); } };
|
||||
};
|
||||
})();
|
||||
33
skills/html-ppt/assets/animations/fx/matrix-rain.js
Normal file
33
skills/html-ppt/assets/animations/fx/matrix-rain.js
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
(function(){
|
||||
window.HPX = window.HPX || {};
|
||||
window.HPX['matrix-rain'] = function(el){
|
||||
const U = window.HPX._u;
|
||||
const k = U.canvas(el), ctx = k.ctx;
|
||||
const glyphs = 'アイウエオカキクケコサシスセソタチツテトナニヌネノ0123456789ABCDEF'.split('');
|
||||
const fs = 16;
|
||||
let cols = 0, drops = [];
|
||||
const init = () => {
|
||||
cols = Math.ceil(k.w/fs);
|
||||
drops = Array.from({length:cols}, () => U.rand(-20, 0));
|
||||
};
|
||||
init();
|
||||
let lw = k.w, lh = k.h;
|
||||
const stop = U.loop(() => {
|
||||
if (k.w!==lw || k.h!==lh){ init(); lw=k.w; lh=k.h; }
|
||||
ctx.fillStyle = 'rgba(0,0,0,0.08)';
|
||||
ctx.fillRect(0,0,k.w,k.h);
|
||||
ctx.font = fs+'px monospace';
|
||||
for (let i=0;i<cols;i++){
|
||||
const ch = glyphs[(Math.random()*glyphs.length)|0];
|
||||
const x = i*fs, y = drops[i]*fs;
|
||||
ctx.fillStyle = '#9fffc9';
|
||||
ctx.fillText(ch, x, y);
|
||||
ctx.fillStyle = '#00ff6a';
|
||||
ctx.fillText(ch, x, y - fs);
|
||||
drops[i] += 1;
|
||||
if (y > k.h && Math.random() > 0.975) drops[i] = 0;
|
||||
}
|
||||
});
|
||||
return { stop(){ stop(); k.destroy(); } };
|
||||
};
|
||||
})();
|
||||
75
skills/html-ppt/assets/animations/fx/neural-net.js
Normal file
75
skills/html-ppt/assets/animations/fx/neural-net.js
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
(function(){
|
||||
window.HPX = window.HPX || {};
|
||||
window.HPX['neural-net'] = function(el){
|
||||
const U = window.HPX._u;
|
||||
const k = U.canvas(el), ctx = k.ctx;
|
||||
const ac = U.accent(el,'#7c5cff'), ac2 = U.accent2(el,'#22d3ee');
|
||||
const layers = [4,6,6,3];
|
||||
let nodes = [], edges = [], pulses = [];
|
||||
const layout = () => {
|
||||
nodes = [];
|
||||
const pad = 40;
|
||||
const cw = k.w - pad*2, ch = k.h - pad*2;
|
||||
for (let L=0; L<layers.length; L++){
|
||||
const x = pad + (cw * L / (layers.length-1));
|
||||
const n = layers[L];
|
||||
for (let i=0;i<n;i++){
|
||||
const y = pad + (ch * (i+0.5) / n);
|
||||
nodes.push({x,y,L,i});
|
||||
}
|
||||
}
|
||||
edges = [];
|
||||
for (let L=0; L<layers.length-1; L++){
|
||||
const a = nodes.filter(n=>n.L===L), b = nodes.filter(n=>n.L===L+1);
|
||||
for (const x of a) for (const y of b) edges.push([nodes.indexOf(x),nodes.indexOf(y)]);
|
||||
}
|
||||
};
|
||||
layout();
|
||||
let lw=k.w, lh=k.h, last=0;
|
||||
const stop = U.loop((t) => {
|
||||
if (k.w!==lw||k.h!==lh){ layout(); lw=k.w; lh=k.h; }
|
||||
ctx.clearRect(0,0,k.w,k.h);
|
||||
ctx.strokeStyle = 'rgba(160,160,200,0.22)'; ctx.lineWidth=1;
|
||||
for (const [i,j] of edges){
|
||||
const a=nodes[i], b=nodes[j];
|
||||
ctx.beginPath(); ctx.moveTo(a.x,a.y); ctx.lineTo(b.x,b.y); ctx.stroke();
|
||||
}
|
||||
if (t - last > 0.25){
|
||||
last = t;
|
||||
const starts = nodes.filter(n=>n.L===0);
|
||||
const s = starts[(Math.random()*starts.length)|0];
|
||||
pulses.push({node:s, L:0, t:0});
|
||||
}
|
||||
pulses = pulses.filter(p => p.L < layers.length-1);
|
||||
for (const p of pulses){
|
||||
p.t += 0.03;
|
||||
if (p.t >= 1){
|
||||
const next = nodes.filter(n=>n.L===p.L+1);
|
||||
p.node2 = next[(Math.random()*next.length)|0];
|
||||
if (!p._started){ p._started = true; }
|
||||
}
|
||||
}
|
||||
// animate progression
|
||||
for (const p of pulses){
|
||||
if (!p.target){
|
||||
const next = nodes.filter(n=>n.L===p.L+1);
|
||||
p.target = next[(Math.random()*next.length)|0];
|
||||
}
|
||||
p.t += 0.04;
|
||||
const a = p.node, b = p.target;
|
||||
const x = a.x + (b.x-a.x)*Math.min(1,p.t);
|
||||
const y = a.y + (b.y-a.y)*Math.min(1,p.t);
|
||||
ctx.fillStyle = ac2;
|
||||
ctx.beginPath(); ctx.arc(x,y,4,0,Math.PI*2); ctx.fill();
|
||||
if (p.t >= 1){ p.node = b; p.target=null; p.L++; p.t=0; }
|
||||
}
|
||||
for (const n of nodes){
|
||||
ctx.fillStyle = ac;
|
||||
ctx.beginPath(); ctx.arc(n.x,n.y,6,0,Math.PI*2); ctx.fill();
|
||||
ctx.strokeStyle = ac2; ctx.lineWidth=1.5;
|
||||
ctx.beginPath(); ctx.arc(n.x,n.y,8,0,Math.PI*2); ctx.stroke();
|
||||
}
|
||||
});
|
||||
return { stop(){ stop(); k.destroy(); } };
|
||||
};
|
||||
})();
|
||||
38
skills/html-ppt/assets/animations/fx/orbit-ring.js
Normal file
38
skills/html-ppt/assets/animations/fx/orbit-ring.js
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
(function(){
|
||||
window.HPX = window.HPX || {};
|
||||
window.HPX['orbit-ring'] = function(el){
|
||||
const U = window.HPX._u;
|
||||
const k = U.canvas(el), ctx = k.ctx;
|
||||
const pal = U.palette(el);
|
||||
const rings = [
|
||||
{r:40, n:3, sp:1.2, c:pal[0]},
|
||||
{r:75, n:5, sp:0.8, c:pal[1]},
|
||||
{r:110, n:8, sp:-0.6, c:pal[2]},
|
||||
{r:145, n:12, sp:0.4, c:pal[3]},
|
||||
{r:180, n:16, sp:-0.3, c:pal[4]}
|
||||
];
|
||||
const stop = U.loop((t) => {
|
||||
ctx.clearRect(0,0,k.w,k.h);
|
||||
const cx=k.w/2, cy=k.h/2;
|
||||
// radial glow
|
||||
const g = ctx.createRadialGradient(cx,cy,0,cx,cy,210);
|
||||
g.addColorStop(0,'rgba(124,92,255,0.25)');
|
||||
g.addColorStop(1,'rgba(0,0,0,0)');
|
||||
ctx.fillStyle = g; ctx.fillRect(0,0,k.w,k.h);
|
||||
for (const R of rings){
|
||||
ctx.strokeStyle = 'rgba(200,200,230,0.2)'; ctx.lineWidth=1;
|
||||
ctx.beginPath(); ctx.arc(cx,cy,R.r,0,Math.PI*2); ctx.stroke();
|
||||
for (let i=0;i<R.n;i++){
|
||||
const a = (i/R.n)*Math.PI*2 + t*R.sp;
|
||||
const x = cx + Math.cos(a)*R.r;
|
||||
const y = cy + Math.sin(a)*R.r;
|
||||
ctx.fillStyle = R.c;
|
||||
ctx.beginPath(); ctx.arc(x,y,4,0,Math.PI*2); ctx.fill();
|
||||
}
|
||||
}
|
||||
ctx.fillStyle = '#fff';
|
||||
ctx.beginPath(); ctx.arc(cx,cy,5,0,Math.PI*2); ctx.fill();
|
||||
});
|
||||
return { stop(){ stop(); k.destroy(); } };
|
||||
};
|
||||
})();
|
||||
42
skills/html-ppt/assets/animations/fx/particle-burst.js
Normal file
42
skills/html-ppt/assets/animations/fx/particle-burst.js
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
(function(){
|
||||
window.HPX = window.HPX || {};
|
||||
window.HPX['particle-burst'] = function(el){
|
||||
const U = window.HPX._u;
|
||||
const k = U.canvas(el), ctx = k.ctx;
|
||||
const pal = U.palette(el);
|
||||
let parts = [];
|
||||
const spawn = () => {
|
||||
const cx = k.w/2, cy = k.h/2;
|
||||
const n = 90;
|
||||
for (let i=0;i<n;i++){
|
||||
const a = Math.random()*Math.PI*2;
|
||||
const s = U.rand(80, 260);
|
||||
parts.push({
|
||||
x: cx, y: cy,
|
||||
vx: Math.cos(a)*s, vy: Math.sin(a)*s,
|
||||
life: 1, r: U.rand(2,5),
|
||||
c: pal[(Math.random()*pal.length)|0]
|
||||
});
|
||||
}
|
||||
};
|
||||
spawn();
|
||||
let lastSpawn = 0;
|
||||
const stop = U.loop((t) => {
|
||||
ctx.clearRect(0,0,k.w,k.h);
|
||||
if (t - lastSpawn > 2.5) { spawn(); lastSpawn = t; }
|
||||
const dt = 1/60;
|
||||
parts = parts.filter(p => p.life > 0);
|
||||
for (const p of parts){
|
||||
p.vy += 220*dt;
|
||||
p.vx *= 0.985; p.vy *= 0.985;
|
||||
p.x += p.vx*dt; p.y += p.vy*dt;
|
||||
p.life -= 0.012;
|
||||
ctx.globalAlpha = Math.max(0, p.life);
|
||||
ctx.fillStyle = p.c;
|
||||
ctx.beginPath(); ctx.arc(p.x, p.y, p.r, 0, Math.PI*2); ctx.fill();
|
||||
}
|
||||
ctx.globalAlpha = 1;
|
||||
});
|
||||
return { stop(){ stop(); k.destroy(); } };
|
||||
};
|
||||
})();
|
||||
39
skills/html-ppt/assets/animations/fx/shockwave.js
Normal file
39
skills/html-ppt/assets/animations/fx/shockwave.js
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
(function(){
|
||||
window.HPX = window.HPX || {};
|
||||
window.HPX['shockwave'] = function(el){
|
||||
const U = window.HPX._u;
|
||||
const k = U.canvas(el), ctx = k.ctx;
|
||||
const ac = U.accent(el,'#7c5cff'), ac2 = U.accent2(el,'#22d3ee');
|
||||
let waves = [];
|
||||
let last = -1;
|
||||
const stop = U.loop((t) => {
|
||||
ctx.fillStyle = 'rgba(0,0,0,0.12)';
|
||||
ctx.fillRect(0,0,k.w,k.h);
|
||||
if (t - last > 0.6){ last = t; waves.push({t:0}); }
|
||||
const cx=k.w/2, cy=k.h/2;
|
||||
const max = Math.hypot(k.w,k.h)/2;
|
||||
waves = waves.filter(w => w.t < 1);
|
||||
for (const w of waves){
|
||||
w.t += 0.012;
|
||||
const r = w.t * max;
|
||||
const alpha = 1 - w.t;
|
||||
ctx.strokeStyle = w.t<0.5?ac2:ac;
|
||||
ctx.globalAlpha = alpha;
|
||||
ctx.lineWidth = 3 + (1-w.t)*3;
|
||||
ctx.beginPath(); ctx.arc(cx,cy,r,0,Math.PI*2); ctx.stroke();
|
||||
ctx.strokeStyle = '#fff';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.globalAlpha = alpha*0.4;
|
||||
ctx.beginPath(); ctx.arc(cx,cy,r*0.92,0,Math.PI*2); ctx.stroke();
|
||||
}
|
||||
ctx.globalAlpha = 1;
|
||||
// core
|
||||
const g = ctx.createRadialGradient(cx,cy,0,cx,cy,40);
|
||||
g.addColorStop(0,'rgba(255,255,255,0.9)');
|
||||
g.addColorStop(1,'rgba(124,92,255,0)');
|
||||
ctx.fillStyle = g;
|
||||
ctx.beginPath(); ctx.arc(cx,cy,40,0,Math.PI*2); ctx.fill();
|
||||
});
|
||||
return { stop(){ stop(); k.destroy(); } };
|
||||
};
|
||||
})();
|
||||
62
skills/html-ppt/assets/animations/fx/sparkle-trail.js
Normal file
62
skills/html-ppt/assets/animations/fx/sparkle-trail.js
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
(function(){
|
||||
window.HPX = window.HPX || {};
|
||||
window.HPX['sparkle-trail'] = function(el){
|
||||
const U = window.HPX._u;
|
||||
const k = U.canvas(el), ctx = k.ctx;
|
||||
k.c.style.pointerEvents = 'none';
|
||||
el.style.cursor = 'crosshair';
|
||||
const pal = U.palette(el);
|
||||
let sparks = [];
|
||||
const onMove = (e) => {
|
||||
const r = el.getBoundingClientRect();
|
||||
const x = e.clientX - r.left, y = e.clientY - r.top;
|
||||
for (let i=0;i<3;i++){
|
||||
sparks.push({
|
||||
x, y,
|
||||
vx: U.rand(-60,60), vy: U.rand(-80,20),
|
||||
life: 1, c: pal[(Math.random()*pal.length)|0],
|
||||
r: U.rand(1.5,3.5)
|
||||
});
|
||||
}
|
||||
};
|
||||
// auto-wiggle if no mouse moves
|
||||
let auto = true, autoT = 0;
|
||||
const onAny = () => { auto = false; };
|
||||
el.addEventListener('pointermove', onMove);
|
||||
el.addEventListener('pointerenter', onAny);
|
||||
const stop = U.loop(() => {
|
||||
ctx.fillStyle = 'rgba(0,0,0,0.15)';
|
||||
ctx.fillRect(0,0,k.w,k.h);
|
||||
if (auto){
|
||||
autoT += 0.04;
|
||||
const x = k.w/2 + Math.cos(autoT)*k.w*0.3;
|
||||
const y = k.h/2 + Math.sin(autoT*1.3)*k.h*0.3;
|
||||
for (let i=0;i<3;i++){
|
||||
sparks.push({
|
||||
x, y,
|
||||
vx: U.rand(-60,60), vy: U.rand(-80,20),
|
||||
life: 1, c: pal[(Math.random()*pal.length)|0],
|
||||
r: U.rand(1.5,3.5)
|
||||
});
|
||||
}
|
||||
}
|
||||
const dt = 1/60;
|
||||
sparks = sparks.filter(s => s.life > 0);
|
||||
for (const s of sparks){
|
||||
s.vy += 160*dt;
|
||||
s.x += s.vx*dt; s.y += s.vy*dt;
|
||||
s.life -= 0.018;
|
||||
ctx.globalAlpha = Math.max(0, s.life);
|
||||
ctx.fillStyle = s.c;
|
||||
ctx.beginPath(); ctx.arc(s.x,s.y,s.r,0,Math.PI*2); ctx.fill();
|
||||
}
|
||||
ctx.globalAlpha = 1;
|
||||
});
|
||||
return { stop(){
|
||||
el.removeEventListener('pointermove', onMove);
|
||||
el.removeEventListener('pointerenter', onAny);
|
||||
el.style.cursor = '';
|
||||
stop(); k.destroy();
|
||||
}};
|
||||
};
|
||||
})();
|
||||
30
skills/html-ppt/assets/animations/fx/starfield.js
Normal file
30
skills/html-ppt/assets/animations/fx/starfield.js
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
(function(){
|
||||
window.HPX = window.HPX || {};
|
||||
window.HPX['starfield'] = function(el){
|
||||
const U = window.HPX._u;
|
||||
const k = U.canvas(el), ctx = k.ctx;
|
||||
const tx = U.text(el, '#ffffff');
|
||||
const N = 260;
|
||||
const stars = Array.from({length:N}, () => ({
|
||||
x: U.rand(-1,1), y: U.rand(-1,1), z: Math.random()
|
||||
}));
|
||||
const stop = U.loop(() => {
|
||||
ctx.fillStyle = 'rgba(0,0,0,0.25)';
|
||||
ctx.fillRect(0,0,k.w,k.h);
|
||||
const cx = k.w/2, cy = k.h/2;
|
||||
for (const s of stars){
|
||||
s.z -= 0.006;
|
||||
if (s.z <= 0.02) { s.x = U.rand(-1,1); s.y = U.rand(-1,1); s.z = 1; }
|
||||
const px = cx + (s.x/s.z)*cx;
|
||||
const py = cy + (s.y/s.z)*cy;
|
||||
if (px<0||py<0||px>k.w||py>k.h) continue;
|
||||
const r = (1-s.z)*2.4;
|
||||
ctx.globalAlpha = 1-s.z;
|
||||
ctx.fillStyle = tx;
|
||||
ctx.beginPath(); ctx.arc(px,py,r,0,Math.PI*2); ctx.fill();
|
||||
}
|
||||
ctx.globalAlpha = 1;
|
||||
});
|
||||
return { stop(){ stop(); k.destroy(); } };
|
||||
};
|
||||
})();
|
||||
51
skills/html-ppt/assets/animations/fx/typewriter-multi.js
Normal file
51
skills/html-ppt/assets/animations/fx/typewriter-multi.js
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
(function(){
|
||||
window.HPX = window.HPX || {};
|
||||
window.HPX['typewriter-multi'] = function(el){
|
||||
if (getComputedStyle(el).position === 'static') el.style.position = 'relative';
|
||||
const lines = [
|
||||
(el.getAttribute('data-fx-line1') || '> initializing knowledge graph...'),
|
||||
(el.getAttribute('data-fx-line2') || '> loading 28 concept nodes'),
|
||||
(el.getAttribute('data-fx-line3') || '> agent ready. awaiting prompt_'),
|
||||
];
|
||||
const wrap = document.createElement('div');
|
||||
wrap.style.cssText = 'position:absolute;inset:0;display:flex;flex-direction:column;justify-content:center;gap:14px;padding:32px 48px;font:600 22px ui-monospace,Menlo,monospace;color:var(--text-1,#e7e7ef);';
|
||||
el.appendChild(wrap);
|
||||
const rows = lines.map((txt) => {
|
||||
const row = document.createElement('div');
|
||||
row.style.cssText = 'white-space:pre;display:flex;align-items:center;';
|
||||
const span = document.createElement('span'); span.textContent = '';
|
||||
const cur = document.createElement('span');
|
||||
cur.textContent = '\u2588';
|
||||
cur.style.cssText = 'display:inline-block;margin-left:2px;color:var(--accent,#22d3ee);animation:hpxBlink 1s steps(2) infinite;';
|
||||
row.appendChild(span); row.appendChild(cur);
|
||||
wrap.appendChild(row);
|
||||
return {row, span, txt, i:0};
|
||||
});
|
||||
// inject blink keyframes once
|
||||
if (!document.getElementById('hpx-blink-kf')){
|
||||
const st = document.createElement('style');
|
||||
st.id = 'hpx-blink-kf';
|
||||
st.textContent = '@keyframes hpxBlink{50%{opacity:0}}';
|
||||
document.head.appendChild(st);
|
||||
}
|
||||
let stopped = false;
|
||||
const speeds = [55, 70, 45];
|
||||
rows.forEach((r, idx) => {
|
||||
const tick = () => {
|
||||
if (stopped) return;
|
||||
if (r.i < r.txt.length){
|
||||
r.span.textContent += r.txt[r.i++];
|
||||
setTimeout(tick, speeds[idx]);
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
if (stopped) return;
|
||||
r.i = 0; r.span.textContent = '';
|
||||
tick();
|
||||
}, 2200);
|
||||
}
|
||||
};
|
||||
setTimeout(tick, idx*400);
|
||||
});
|
||||
return { stop(){ stopped = true; if (wrap.parentNode) wrap.parentNode.removeChild(wrap); } };
|
||||
};
|
||||
})();
|
||||
47
skills/html-ppt/assets/animations/fx/word-cascade.js
Normal file
47
skills/html-ppt/assets/animations/fx/word-cascade.js
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
(function(){
|
||||
window.HPX = window.HPX || {};
|
||||
window.HPX['word-cascade'] = function(el){
|
||||
const U = window.HPX._u;
|
||||
const k = U.canvas(el), ctx = k.ctx;
|
||||
const pal = U.palette(el);
|
||||
const WORDS = ['AI','知识','Graph','Claude','LLM','Agent','Vector','RAG','Token','神经',
|
||||
'Prompt','Chain','Skill','Code','Cloud','GPU','Flow','推理','Data','Model'];
|
||||
let items = [];
|
||||
let last = -1;
|
||||
let piles = {}; // column -> stack height
|
||||
const stop = U.loop((t) => {
|
||||
ctx.clearRect(0,0,k.w,k.h);
|
||||
if (t - last > 0.18){
|
||||
last = t;
|
||||
const w = WORDS[(Math.random()*WORDS.length)|0];
|
||||
items.push({
|
||||
text: w, x: U.rand(40, k.w-40), y: -20,
|
||||
vy: 0, c: pal[(Math.random()*pal.length)|0],
|
||||
size: U.rand(16,26), landed: false
|
||||
});
|
||||
}
|
||||
ctx.textAlign='center'; ctx.textBaseline='middle';
|
||||
for (const it of items){
|
||||
if (!it.landed){
|
||||
it.vy += 0.4;
|
||||
it.y += it.vy;
|
||||
const col = Math.round(it.x/60);
|
||||
const floor = k.h - (piles[col]||0) - it.size*0.6;
|
||||
if (it.y >= floor){
|
||||
it.y = floor; it.landed = true;
|
||||
piles[col] = (piles[col]||0) + it.size*1.1;
|
||||
if ((piles[col]||0) > k.h*0.8) piles[col] = 0; // reset if too high
|
||||
}
|
||||
}
|
||||
ctx.fillStyle = it.c;
|
||||
ctx.font = `700 ${it.size}px system-ui,sans-serif`;
|
||||
ctx.fillText(it.text, it.x, it.y);
|
||||
}
|
||||
// prune old landed
|
||||
if (items.length > 120){
|
||||
items = items.filter(i => !i.landed).concat(items.filter(i=>i.landed).slice(-60));
|
||||
}
|
||||
});
|
||||
return { stop(){ stop(); k.destroy(); } };
|
||||
};
|
||||
})();
|
||||
150
skills/html-ppt/assets/base.css
Normal file
150
skills/html-ppt/assets/base.css
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
/* html-ppt :: base.css — reset + shared tokens + layout primitives */
|
||||
/* Default tokens. Themes in assets/themes/*.css override the :root block. */
|
||||
:root {
|
||||
--bg: #ffffff;
|
||||
--bg-soft: #f7f7f8;
|
||||
--surface: #ffffff;
|
||||
--surface-2: #f2f2f4;
|
||||
--border: rgba(0,0,0,.08);
|
||||
--border-strong: rgba(0,0,0,.16);
|
||||
--text-1: #111216;
|
||||
--text-2: #55596a;
|
||||
--text-3: #8a8f9e;
|
||||
--accent: #3b6cff;
|
||||
--accent-2: #7a5cff;
|
||||
--accent-3: #ff5c8a;
|
||||
--good: #1aaf6c;
|
||||
--warn: #f5a524;
|
||||
--bad: #e0445a;
|
||||
--grad: linear-gradient(135deg,#3b6cff,#7a5cff 55%,#ff5c8a);
|
||||
--grad-soft: linear-gradient(135deg,#eef2ff,#f5ecff 55%,#ffeef5);
|
||||
--radius: 18px;
|
||||
--radius-sm: 12px;
|
||||
--radius-lg: 26px;
|
||||
--shadow: 0 10px 30px rgba(18,24,40,.08), 0 2px 6px rgba(18,24,40,.04);
|
||||
--shadow-lg: 0 24px 60px rgba(18,24,40,.14), 0 6px 16px rgba(18,24,40,.06);
|
||||
--font-sans: 'Inter','Noto Sans SC',-apple-system,BlinkMacSystemFont,Helvetica,Arial,sans-serif;
|
||||
--font-serif: 'Playfair Display','Noto Serif SC',Georgia,serif;
|
||||
--font-mono: 'JetBrains Mono','IBM Plex Mono',SFMono-Regular,Menlo,monospace;
|
||||
--font-display: var(--font-sans);
|
||||
--letter-tight: -.03em;
|
||||
--letter-normal: -.01em;
|
||||
--ease: cubic-bezier(.4,0,.2,1);
|
||||
}
|
||||
|
||||
*,*::before,*::after{box-sizing:border-box}
|
||||
html,body{margin:0;padding:0;background:var(--bg);color:var(--text-1);
|
||||
font-family:var(--font-sans);font-weight:400;line-height:1.6;
|
||||
-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;
|
||||
letter-spacing:var(--letter-normal)}
|
||||
img,svg,video{max-width:100%;display:block}
|
||||
a{color:var(--accent);text-decoration:none}
|
||||
a:hover{text-decoration:underline}
|
||||
code,kbd,pre,samp{font-family:var(--font-mono)}
|
||||
|
||||
/* ================= SLIDE SYSTEM ================= */
|
||||
.deck{position:relative;width:100vw;height:100vh;overflow:hidden;background:var(--bg)}
|
||||
.slide{
|
||||
position:absolute;inset:0;
|
||||
display:flex;flex-direction:column;justify-content:center;
|
||||
padding:72px 96px;
|
||||
box-sizing:border-box;
|
||||
opacity:0;pointer-events:none;
|
||||
transition:opacity .5s var(--ease), transform .5s var(--ease);
|
||||
transform:translateX(30px);
|
||||
overflow:hidden;
|
||||
}
|
||||
.slide.is-active{opacity:1;pointer-events:auto;transform:translateX(0);z-index:2}
|
||||
.slide.is-prev{transform:translateX(-30px)}
|
||||
|
||||
/* single-page standalone (used when a layout file is opened directly) */
|
||||
body.single .slide{position:relative;width:100vw;height:100vh;opacity:1;transform:none;pointer-events:auto}
|
||||
|
||||
/* ================= TYPOGRAPHY ================= */
|
||||
.eyebrow{font-size:13px;font-weight:500;letter-spacing:.16em;text-transform:uppercase;color:var(--text-3)}
|
||||
.kicker{font-size:14px;font-weight:600;color:var(--accent);letter-spacing:.08em;text-transform:uppercase}
|
||||
h1.title,.h1{font-family:var(--font-display);font-size:72px;line-height:1.05;font-weight:800;letter-spacing:var(--letter-tight);margin:0 0 18px;color:var(--text-1)}
|
||||
h2.title,.h2{font-family:var(--font-display);font-size:54px;line-height:1.1;font-weight:700;letter-spacing:var(--letter-tight);margin:0 0 14px}
|
||||
h3,.h3{font-size:32px;line-height:1.2;font-weight:600;letter-spacing:var(--letter-normal);margin:0 0 10px}
|
||||
h4,.h4{font-size:22px;line-height:1.3;font-weight:600;margin:0 0 8px}
|
||||
.lede{font-size:22px;line-height:1.55;color:var(--text-2);font-weight:300;max-width:62ch}
|
||||
.dim{color:var(--text-2)}
|
||||
.dim2{color:var(--text-3)}
|
||||
.mono{font-family:var(--font-mono)}
|
||||
.serif{font-family:var(--font-serif)}
|
||||
.gradient-text{background:var(--grad);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;color:transparent}
|
||||
|
||||
/* ================= LAYOUT PRIMITIVES ================= */
|
||||
.stack>*+*{margin-top:14px}
|
||||
.row{display:flex;gap:24px;align-items:center}
|
||||
.row.wrap{flex-wrap:wrap}
|
||||
.grid{display:grid;gap:24px}
|
||||
.g2{grid-template-columns:repeat(2,1fr)}
|
||||
.g3{grid-template-columns:repeat(3,1fr)}
|
||||
.g4{grid-template-columns:repeat(4,1fr)}
|
||||
.center{display:flex;align-items:center;justify-content:center;text-align:center}
|
||||
.fill{flex:1}
|
||||
.sp-t{padding-top:24px}.sp-b{padding-bottom:24px}
|
||||
.mt-s{margin-top:8px}.mt-m{margin-top:18px}.mt-l{margin-top:32px}
|
||||
.mb-s{margin-bottom:8px}.mb-m{margin-bottom:18px}.mb-l{margin-bottom:32px}
|
||||
|
||||
/* ================= CARDS ================= */
|
||||
.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);
|
||||
padding:26px 28px;box-shadow:var(--shadow);position:relative;overflow:hidden}
|
||||
.card-soft{background:var(--surface-2);border:1px solid var(--border)}
|
||||
.card-outline{background:transparent;border:1.5px solid var(--border-strong);box-shadow:none}
|
||||
.card-accent{background:var(--surface);border-top:3px solid var(--accent)}
|
||||
.card-hover{transition:transform .3s var(--ease),box-shadow .3s var(--ease)}
|
||||
.card-hover:hover{transform:translateY(-4px);box-shadow:var(--shadow-lg)}
|
||||
|
||||
.pill{display:inline-block;padding:4px 12px;border-radius:999px;font-size:12px;font-weight:500;
|
||||
background:var(--surface-2);color:var(--text-2);border:1px solid var(--border)}
|
||||
.pill-accent{background:color-mix(in srgb,var(--accent) 12%,transparent);color:var(--accent);border-color:color-mix(in srgb,var(--accent) 28%,transparent)}
|
||||
|
||||
/* ================= BARS / DIVIDERS ================= */
|
||||
.divider{height:1px;background:var(--border);width:100%}
|
||||
.divider-accent{height:3px;width:72px;background:var(--accent);border-radius:2px}
|
||||
|
||||
/* ================= CHROME (header/footer/progress) ================= */
|
||||
.deck-header{position:absolute;top:24px;left:40px;right:40px;display:flex;align-items:center;justify-content:space-between;
|
||||
font-size:12px;color:var(--text-3);letter-spacing:.12em;text-transform:uppercase;z-index:10;pointer-events:none}
|
||||
.deck-footer{position:absolute;bottom:24px;left:40px;right:40px;display:flex;align-items:center;justify-content:space-between;
|
||||
font-size:12px;color:var(--text-3);z-index:10;pointer-events:none}
|
||||
.slide-number::before{content:attr(data-current)}
|
||||
.slide-number::after{content:" / " attr(data-total)}
|
||||
.progress-bar{position:fixed;left:0;right:0;bottom:0;height:3px;background:transparent;z-index:20}
|
||||
.progress-bar > span{display:block;height:100%;width:0;background:var(--accent);transition:width .3s var(--ease)}
|
||||
|
||||
/* ================= PRESENTER / OVERVIEW ================= */
|
||||
.notes{display:none!important}
|
||||
.notes-overlay{position:fixed;inset:auto 0 0 0;max-height:42vh;background:rgba(20,22,30,.95);color:#e8ebf4;
|
||||
padding:20px 32px;font-size:16px;line-height:1.6;border-top:1px solid rgba(255,255,255,.1);transform:translateY(100%);
|
||||
transition:transform .3s var(--ease);z-index:40;overflow:auto;font-family:var(--font-sans)}
|
||||
.notes-overlay.open{transform:translateY(0)}
|
||||
.overview{position:fixed;inset:0;background:rgba(10,12,18,.92);backdrop-filter:blur(12px);z-index:50;
|
||||
display:none;padding:40px;overflow:auto}
|
||||
.overview.open{display:grid;grid-template-columns:repeat(4,1fr);gap:20px;align-content:start}
|
||||
.overview .thumb{background:var(--surface);border:1px solid var(--border);border-radius:12px;
|
||||
aspect-ratio:16/9;overflow:hidden;cursor:pointer;position:relative;color:var(--text-1);padding:16px;
|
||||
font-size:11px;transition:transform .2s var(--ease)}
|
||||
.overview .thumb:hover{transform:scale(1.04)}
|
||||
.overview .thumb .n{position:absolute;top:8px;left:10px;font-weight:700;font-size:14px;color:var(--text-3)}
|
||||
.overview .thumb .t{position:absolute;bottom:10px;left:14px;right:14px;font-weight:600;color:var(--text-1)}
|
||||
|
||||
/* ================= PRESENTER VIEW ================= */
|
||||
/* Presenter view opens in a separate popup window (S key).
|
||||
* All presenter styles are self-contained in the popup HTML generated by runtime.js.
|
||||
* The audience window (this file) is NOT affected — it stays as normal deck view.
|
||||
* Only the .notes class below is needed to hide speaker notes from audience. */
|
||||
|
||||
/* ================= UTILITY ================= */
|
||||
.hidden{display:none!important}
|
||||
.nowrap{white-space:nowrap}
|
||||
.tr{text-align:right}.tc{text-align:center}.tl{text-align:left}
|
||||
.uppercase{text-transform:uppercase;letter-spacing:.12em}
|
||||
|
||||
/* ================= PRINT ================= */
|
||||
@media print{
|
||||
.slide{position:relative;opacity:1!important;transform:none!important;page-break-after:always;height:100vh}
|
||||
.deck-header,.deck-footer,.progress-bar,.notes-overlay,.overview{display:none!important}
|
||||
}
|
||||
9
skills/html-ppt/assets/fonts.css
Normal file
9
skills/html-ppt/assets/fonts.css
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
/* html-ppt :: shared webfonts */
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@200;300;400;500;600;700;800;900&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@200;300;400;500;600;700;900&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@300;400;600;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,600;0,800;1,400&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@300;400;500;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Archivo+Black&display=swap');
|
||||
960
skills/html-ppt/assets/runtime.js
Normal file
960
skills/html-ppt/assets/runtime.js
Normal file
|
|
@ -0,0 +1,960 @@
|
|||
/* html-ppt :: runtime.js
|
||||
* Keyboard-driven deck runtime. Zero dependencies.
|
||||
*
|
||||
* Features:
|
||||
* ← → / space / PgUp PgDn / Home End navigation
|
||||
* F fullscreen
|
||||
* S presenter mode (opens a NEW WINDOW with current/next slide preview + notes + timer)
|
||||
* The original window stays as audience view, synced via BroadcastChannel.
|
||||
* Slide previews use CSS transform:scale() at design resolution for pixel-perfect layout.
|
||||
* N quick notes overlay (bottom drawer)
|
||||
* O slide overview grid
|
||||
* T cycle themes (reads data-themes on <html> or <body>)
|
||||
* A cycle demo animation on current slide
|
||||
* URL hash #/N deep-link to slide N (1-based)
|
||||
* Progress bar auto-managed
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
const ANIMS = ['fade-up','fade-down','fade-left','fade-right','rise-in','drop-in',
|
||||
'zoom-pop','blur-in','glitch-in','typewriter','neon-glow','shimmer-sweep',
|
||||
'gradient-flow','stagger-list','counter-up','path-draw','parallax-tilt',
|
||||
'card-flip-3d','cube-rotate-3d','page-turn-3d','perspective-zoom',
|
||||
'marquee-scroll','kenburns','confetti-burst','spotlight','morph-shape','ripple-reveal'];
|
||||
|
||||
function ready(fn){ if(document.readyState!='loading')fn(); else document.addEventListener('DOMContentLoaded',fn);}
|
||||
|
||||
/* ========== Parse URL for preview-only mode ==========
|
||||
* When loaded as iframe.src = "index.html?preview=3", runtime enters a
|
||||
* locked single-slide mode: only slide N is visible, no chrome, no keys,
|
||||
* no hash updates. This is how the presenter window shows pixel-perfect
|
||||
* previews — by loading the actual deck file in an iframe and telling it
|
||||
* to display only a specific slide.
|
||||
*/
|
||||
function getPreviewIdx() {
|
||||
const m = /[?&]preview=(\d+)/.exec(location.search || '');
|
||||
return m ? parseInt(m[1], 10) - 1 : -1;
|
||||
}
|
||||
|
||||
ready(function () {
|
||||
const deck = document.querySelector('.deck');
|
||||
if (!deck) return;
|
||||
const slides = Array.from(deck.querySelectorAll('.slide'));
|
||||
if (!slides.length) return;
|
||||
|
||||
const previewOnlyIdx = getPreviewIdx();
|
||||
const isPreviewMode = previewOnlyIdx >= 0 && previewOnlyIdx < slides.length;
|
||||
|
||||
/* ===== Preview-only mode: show one slide, hide everything else ===== */
|
||||
if (isPreviewMode) {
|
||||
function showSlide(i) {
|
||||
slides.forEach((s, j) => {
|
||||
const active = (j === i);
|
||||
s.classList.toggle('is-active', active);
|
||||
s.style.display = active ? '' : 'none';
|
||||
if (active) {
|
||||
s.style.opacity = '1';
|
||||
s.style.transform = 'none';
|
||||
s.style.pointerEvents = 'auto';
|
||||
}
|
||||
});
|
||||
}
|
||||
showSlide(previewOnlyIdx);
|
||||
/* Hide chrome that the presenter shouldn't see in preview */
|
||||
const hideSel = '.progress-bar, .notes-overlay, .overview, .notes, aside.notes, .speaker-notes';
|
||||
document.querySelectorAll(hideSel).forEach(el => { el.style.display = 'none'; });
|
||||
document.documentElement.setAttribute('data-preview', '1');
|
||||
document.body.setAttribute('data-preview', '1');
|
||||
/* Auto-detect theme base path for theme switching in preview mode */
|
||||
function getPreviewThemeBase() {
|
||||
const base = document.documentElement.getAttribute('data-theme-base');
|
||||
if (base) return base;
|
||||
const tl = document.getElementById('theme-link');
|
||||
if (tl) {
|
||||
const raw = tl.getAttribute('href') || '';
|
||||
const ls = raw.lastIndexOf('/');
|
||||
if (ls >= 0) return raw.substring(0, ls + 1);
|
||||
}
|
||||
return 'assets/themes/';
|
||||
}
|
||||
const previewThemeBase = getPreviewThemeBase();
|
||||
|
||||
/* Listen for postMessage from parent presenter window:
|
||||
* - preview-goto: switch visible slide WITHOUT reloading
|
||||
* - preview-theme: switch theme CSS link to match audience window */
|
||||
window.addEventListener('message', function(e) {
|
||||
if (!e.data) return;
|
||||
if (e.data.type === 'preview-goto') {
|
||||
const n = parseInt(e.data.idx, 10);
|
||||
if (n >= 0 && n < slides.length) showSlide(n);
|
||||
} else if (e.data.type === 'preview-theme' && e.data.name) {
|
||||
let link = document.getElementById('theme-link');
|
||||
if (!link) {
|
||||
link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.id = 'theme-link';
|
||||
document.head.appendChild(link);
|
||||
}
|
||||
link.href = previewThemeBase + e.data.name + '.css';
|
||||
document.documentElement.setAttribute('data-theme', e.data.name);
|
||||
}
|
||||
});
|
||||
/* Signal to parent that preview iframe is ready */
|
||||
try { window.parent && window.parent.postMessage({ type: 'preview-ready' }, '*'); } catch(e) {}
|
||||
return;
|
||||
}
|
||||
|
||||
let idx = 0;
|
||||
const total = slides.length;
|
||||
|
||||
/* ===== BroadcastChannel for presenter sync ===== */
|
||||
const CHANNEL_NAME = 'html-ppt-presenter-' + location.pathname;
|
||||
let bc;
|
||||
try { bc = new BroadcastChannel(CHANNEL_NAME); } catch(e) { bc = null; }
|
||||
|
||||
// Are we running inside the presenter popup? (legacy flag, now unused)
|
||||
const isPresenterWindow = false;
|
||||
|
||||
/* ===== progress bar ===== */
|
||||
let bar = document.querySelector('.progress-bar');
|
||||
if (!bar) {
|
||||
bar = document.createElement('div');
|
||||
bar.className = 'progress-bar';
|
||||
bar.innerHTML = '<span></span>';
|
||||
document.body.appendChild(bar);
|
||||
}
|
||||
const barFill = bar.querySelector('span');
|
||||
|
||||
/* ===== notes overlay (N key) ===== */
|
||||
let notes = document.querySelector('.notes-overlay');
|
||||
if (!notes) {
|
||||
notes = document.createElement('div');
|
||||
notes.className = 'notes-overlay';
|
||||
document.body.appendChild(notes);
|
||||
}
|
||||
|
||||
/* ===== overview grid (O key) ===== */
|
||||
let overview = document.querySelector('.overview');
|
||||
if (!overview) {
|
||||
overview = document.createElement('div');
|
||||
overview.className = 'overview';
|
||||
slides.forEach((s, i) => {
|
||||
const t = document.createElement('div');
|
||||
t.className = 'thumb';
|
||||
// Force 16:9 aspect ratio robustly
|
||||
t.style.padding = '0 0 56.25% 0';
|
||||
t.style.height = '0';
|
||||
t.style.position = 'relative';
|
||||
t.style.overflow = 'hidden';
|
||||
|
||||
const title = s.getAttribute('data-title') ||
|
||||
(s.querySelector('h1,h2,h3')||{}).textContent || ('Slide '+(i+1));
|
||||
|
||||
// Create a container for the mini-slide
|
||||
const mini = document.createElement('div');
|
||||
mini.className = 'mini-slide';
|
||||
mini.style.position = 'absolute';
|
||||
mini.style.top = '0';
|
||||
mini.style.left = '0';
|
||||
mini.style.width = '1920px';
|
||||
mini.style.height = '1080px';
|
||||
mini.style.transformOrigin = 'top left';
|
||||
mini.style.pointerEvents = 'none';
|
||||
mini.style.background = 'var(--bg)';
|
||||
|
||||
// Clone the slide content
|
||||
const clone = s.cloneNode(true);
|
||||
clone.className = 'slide is-active'; // force active styles
|
||||
clone.style.position = 'absolute';
|
||||
clone.style.inset = '0';
|
||||
clone.style.transform = 'none';
|
||||
clone.style.opacity = '1';
|
||||
clone.style.padding = '72px 96px'; // ensure padding is kept
|
||||
|
||||
mini.appendChild(clone);
|
||||
t.appendChild(mini);
|
||||
|
||||
// Add the number and title overlay
|
||||
const overlay = document.createElement('div');
|
||||
overlay.style.position = 'absolute';
|
||||
overlay.style.inset = '0';
|
||||
overlay.style.background = 'linear-gradient(to bottom, rgba(0,0,0,0.2) 0%, transparent 40%, transparent 60%, rgba(0,0,0,0.8) 100%)';
|
||||
overlay.style.color = '#fff';
|
||||
overlay.style.zIndex = '10';
|
||||
overlay.style.pointerEvents = 'none';
|
||||
|
||||
const n = document.createElement('div');
|
||||
n.className = 'n';
|
||||
n.textContent = i + 1;
|
||||
n.style.position = 'absolute';
|
||||
n.style.top = '12px';
|
||||
n.style.left = '16px';
|
||||
n.style.fontWeight = '700';
|
||||
n.style.fontSize = '16px';
|
||||
n.style.color = '#fff';
|
||||
n.style.textShadow = '0 1px 4px rgba(0,0,0,0.8)';
|
||||
|
||||
const text = document.createElement('div');
|
||||
text.className = 't';
|
||||
text.textContent = title.trim().slice(0,80);
|
||||
text.style.position = 'absolute';
|
||||
text.style.bottom = '12px';
|
||||
text.style.left = '16px';
|
||||
text.style.right = '16px';
|
||||
text.style.fontWeight = '600';
|
||||
text.style.fontSize = '14px';
|
||||
text.style.color = '#fff';
|
||||
text.style.textShadow = '0 1px 4px rgba(0,0,0,0.8)';
|
||||
|
||||
overlay.appendChild(n);
|
||||
overlay.appendChild(text);
|
||||
t.appendChild(overlay);
|
||||
|
||||
t.addEventListener('click', () => { go(i); toggleOverview(false); });
|
||||
overview.appendChild(t);
|
||||
});
|
||||
document.body.appendChild(overview);
|
||||
}
|
||||
|
||||
/* ===== navigation ===== */
|
||||
function go(n, fromRemote){
|
||||
n = Math.max(0, Math.min(total-1, n));
|
||||
slides.forEach((s,i) => {
|
||||
s.classList.toggle('is-active', i===n);
|
||||
s.classList.toggle('is-prev', i<n);
|
||||
});
|
||||
idx = n;
|
||||
barFill.style.width = ((n+1)/total*100)+'%';
|
||||
const numEl = document.querySelector('.slide-number');
|
||||
if (numEl) { numEl.setAttribute('data-current', n+1); numEl.setAttribute('data-total', total); }
|
||||
|
||||
// notes (bottom overlay)
|
||||
const note = slides[n].querySelector('.notes, aside.notes, .speaker-notes');
|
||||
notes.innerHTML = note ? note.innerHTML : '';
|
||||
|
||||
// hash
|
||||
const hashTarget = '#/'+(n+1);
|
||||
if (location.hash !== hashTarget && !isPresenterWindow) {
|
||||
history.replaceState(null,'', hashTarget);
|
||||
}
|
||||
|
||||
// re-trigger entry animations
|
||||
slides[n].querySelectorAll('[data-anim]').forEach(el => {
|
||||
const a = el.getAttribute('data-anim');
|
||||
el.classList.remove('anim-'+a);
|
||||
void el.offsetWidth;
|
||||
el.classList.add('anim-'+a);
|
||||
});
|
||||
|
||||
// counter-up
|
||||
slides[n].querySelectorAll('.counter').forEach(el => {
|
||||
const target = parseFloat(el.getAttribute('data-to')||el.textContent);
|
||||
const dur = parseInt(el.getAttribute('data-dur')||'1200',10);
|
||||
const start = performance.now();
|
||||
const from = 0;
|
||||
function tick(now){
|
||||
const t = Math.min(1,(now-start)/dur);
|
||||
const v = from + (target-from)*(1-Math.pow(1-t,3));
|
||||
el.textContent = (target % 1 === 0) ? Math.round(v) : v.toFixed(1);
|
||||
if (t<1) requestAnimationFrame(tick);
|
||||
}
|
||||
requestAnimationFrame(tick);
|
||||
});
|
||||
|
||||
// Broadcast to other window (audience ↔ presenter)
|
||||
if (!fromRemote && bc) {
|
||||
bc.postMessage({ type: 'go', idx: n });
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== listen for remote navigation / theme changes ===== */
|
||||
if (bc) {
|
||||
bc.onmessage = function(e) {
|
||||
if (!e.data) return;
|
||||
if (e.data.type === 'go' && typeof e.data.idx === 'number') {
|
||||
go(e.data.idx, true);
|
||||
} else if (e.data.type === 'theme' && e.data.name) {
|
||||
/* Sync theme across windows */
|
||||
const i = themes.indexOf(e.data.name);
|
||||
if (i >= 0) themeIdx = i;
|
||||
applyTheme(e.data.name);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function toggleNotes(force){ notes.classList.toggle('open', force!==undefined?force:!notes.classList.contains('open')); }
|
||||
function toggleOverview(force){
|
||||
const isOpen = force!==undefined ? force : !overview.classList.contains('open');
|
||||
overview.classList.toggle('open', isOpen);
|
||||
if (isOpen) {
|
||||
requestAnimationFrame(() => {
|
||||
const thumbs = overview.querySelectorAll('.thumb');
|
||||
if (thumbs.length) {
|
||||
const scale = thumbs[0].clientWidth / 1920;
|
||||
overview.querySelectorAll('.mini-slide').forEach(m => {
|
||||
m.style.transform = 'scale(' + scale + ')';
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/* ========== PRESENTER MODE — Magnetic-card popup window ========== */
|
||||
/* Opens a new window with 4 draggable, resizable cards:
|
||||
* CURRENT — iframe(?preview=N) pixel-perfect preview of current slide
|
||||
* NEXT — iframe(?preview=N+1) pixel-perfect preview of next slide
|
||||
* SCRIPT — large speaker notes (逐字稿)
|
||||
* TIMER — elapsed timer + page counter + controls
|
||||
* Cards remember position/size in localStorage.
|
||||
* Two windows sync via BroadcastChannel.
|
||||
*/
|
||||
let presenterWin = null;
|
||||
|
||||
function openPresenterWindow() {
|
||||
if (presenterWin && !presenterWin.closed) {
|
||||
presenterWin.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
// Build absolute URL of THIS deck file (without hash/query)
|
||||
const deckUrl = location.protocol + '//' + location.host + location.pathname;
|
||||
|
||||
// Collect slide titles + notes (HTML strings)
|
||||
const slideMeta = slides.map((s, i) => {
|
||||
const note = s.querySelector('.notes, aside.notes, .speaker-notes');
|
||||
return {
|
||||
title: s.getAttribute('data-title') ||
|
||||
(s.querySelector('h1,h2,h3')||{}).textContent || ('Slide '+(i+1)),
|
||||
notes: note ? note.innerHTML : ''
|
||||
};
|
||||
});
|
||||
|
||||
/* Capture current theme so presenter previews match the audience */
|
||||
const currentTheme = root.getAttribute('data-theme') || (themes[themeIdx] || '');
|
||||
const presenterHTML = buildPresenterHTML(deckUrl, slideMeta, total, idx, CHANNEL_NAME, currentTheme);
|
||||
|
||||
presenterWin = window.open('', 'html-ppt-presenter', 'width=1280,height=820,menubar=no,toolbar=no');
|
||||
if (!presenterWin) {
|
||||
alert('请允许弹出窗口以使用演讲者视图');
|
||||
return;
|
||||
}
|
||||
presenterWin.document.open();
|
||||
presenterWin.document.write(presenterHTML);
|
||||
presenterWin.document.close();
|
||||
}
|
||||
|
||||
function buildPresenterHTML(deckUrl, slideMeta, total, startIdx, channelName, currentTheme) {
|
||||
const metaJSON = JSON.stringify(slideMeta);
|
||||
const deckUrlJSON = JSON.stringify(deckUrl);
|
||||
const channelJSON = JSON.stringify(channelName);
|
||||
const themeJSON = JSON.stringify(currentTheme || '');
|
||||
const storageKey = 'html-ppt-presenter:' + location.pathname;
|
||||
|
||||
// Build the document as a single template string for clarity
|
||||
return `<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Presenter View</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
html, body {
|
||||
width: 100%; height: 100%; overflow: hidden;
|
||||
background: #1a1d24;
|
||||
background-image:
|
||||
radial-gradient(circle at 20% 30%, rgba(88,166,255,.04), transparent 50%),
|
||||
radial-gradient(circle at 80% 70%, rgba(188,140,255,.04), transparent 50%);
|
||||
color: #e6edf3;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans SC", sans-serif;
|
||||
}
|
||||
/* Stage: positioned area where cards live */
|
||||
#stage { position: absolute; inset: 0; overflow: hidden; }
|
||||
|
||||
/* Magnetic card */
|
||||
.pcard {
|
||||
position: absolute;
|
||||
background: #0d1117;
|
||||
border: 1px solid rgba(255,255,255,.1);
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 8px 32px rgba(0,0,0,.45), 0 0 0 1px rgba(255,255,255,.02);
|
||||
display: flex; flex-direction: column;
|
||||
overflow: hidden;
|
||||
min-width: 180px; min-height: 100px;
|
||||
transition: box-shadow .2s, border-color .2s;
|
||||
}
|
||||
.pcard.dragging { box-shadow: 0 16px 48px rgba(0,0,0,.6), 0 0 0 2px rgba(88,166,255,.5); border-color: #58a6ff; transition: none; z-index: 9999; }
|
||||
.pcard.resizing { box-shadow: 0 16px 48px rgba(0,0,0,.6), 0 0 0 2px rgba(63,185,80,.5); border-color: #3fb950; transition: none; z-index: 9999; }
|
||||
.pcard:hover { border-color: rgba(88,166,255,.3); }
|
||||
|
||||
/* Card header (drag handle) */
|
||||
.pcard-head {
|
||||
display: flex; align-items: center; gap: 10px;
|
||||
padding: 8px 12px;
|
||||
background: rgba(255,255,255,.04);
|
||||
border-bottom: 1px solid rgba(255,255,255,.06);
|
||||
cursor: move;
|
||||
user-select: none;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.pcard-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--dot-color, #58a6ff); flex-shrink: 0; }
|
||||
.pcard-title {
|
||||
font-size: 11px; letter-spacing: .15em; text-transform: uppercase;
|
||||
font-weight: 700; color: #8b949e; flex: 1;
|
||||
}
|
||||
.pcard-meta { font-size: 11px; color: #6e7681; }
|
||||
|
||||
/* Card body */
|
||||
.pcard-body { flex: 1; position: relative; overflow: hidden; min-height: 0; }
|
||||
|
||||
/* Preview cards (CURRENT/NEXT) — iframe-based pixel-perfect render */
|
||||
.pcard-preview .pcard-body { background: #000; }
|
||||
.pcard-preview iframe {
|
||||
position: absolute; top: 0; left: 0;
|
||||
width: 1920px; height: 1080px;
|
||||
border: none;
|
||||
transform-origin: top left;
|
||||
pointer-events: none;
|
||||
background: transparent;
|
||||
}
|
||||
.pcard-preview .preview-end {
|
||||
position: absolute; inset: 0;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
color: #484f58; font-size: 14px; letter-spacing: .12em;
|
||||
}
|
||||
|
||||
/* Notes card */
|
||||
.pcard-notes .pcard-body {
|
||||
padding: 14px 18px;
|
||||
overflow-y: auto;
|
||||
font-size: 18px; line-height: 1.75;
|
||||
color: #d0d7de;
|
||||
font-family: "Noto Sans SC", -apple-system, sans-serif;
|
||||
}
|
||||
.pcard-notes .pcard-body p { margin: 0 0 .7em 0; }
|
||||
.pcard-notes .pcard-body strong { color: #f0883e; }
|
||||
.pcard-notes .pcard-body em { color: #58a6ff; font-style: normal; }
|
||||
.pcard-notes .pcard-body code {
|
||||
font-family: "SF Mono", monospace; font-size: .9em;
|
||||
background: rgba(255,255,255,.08); padding: 1px 6px; border-radius: 4px;
|
||||
}
|
||||
.pcard-notes .empty { color: #484f58; font-style: italic; }
|
||||
|
||||
/* Timer card */
|
||||
.pcard-timer .pcard-body {
|
||||
display: flex; flex-direction: column; gap: 14px;
|
||||
padding: 18px 20px; justify-content: center;
|
||||
}
|
||||
.timer-display {
|
||||
font-family: "SF Mono", "JetBrains Mono", monospace;
|
||||
font-size: 42px; font-weight: 700;
|
||||
color: #3fb950;
|
||||
letter-spacing: .04em;
|
||||
line-height: 1;
|
||||
}
|
||||
.timer-row {
|
||||
display: flex; align-items: center; gap: 12px;
|
||||
font-size: 14px; color: #8b949e;
|
||||
}
|
||||
.timer-row .label { font-size: 10px; letter-spacing: .15em; text-transform: uppercase; color: #6e7681; }
|
||||
.timer-row .val { color: #e6edf3; font-weight: 600; font-family: "SF Mono", monospace; }
|
||||
.timer-controls { display: flex; gap: 8px; flex-wrap: wrap; }
|
||||
.timer-btn {
|
||||
background: rgba(255,255,255,.06);
|
||||
border: 1px solid rgba(255,255,255,.1);
|
||||
color: #e6edf3;
|
||||
padding: 6px 12px;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
font-family: inherit;
|
||||
}
|
||||
.timer-btn:hover { background: rgba(88,166,255,.15); border-color: #58a6ff; }
|
||||
.timer-btn:active { transform: translateY(1px); }
|
||||
|
||||
/* Resize handle */
|
||||
.pcard-resize {
|
||||
position: absolute; right: 0; bottom: 0;
|
||||
width: 18px; height: 18px;
|
||||
cursor: nwse-resize;
|
||||
background: linear-gradient(135deg, transparent 50%, rgba(255,255,255,.25) 50%, rgba(255,255,255,.25) 60%, transparent 60%, transparent 70%, rgba(255,255,255,.25) 70%, rgba(255,255,255,.25) 80%, transparent 80%);
|
||||
z-index: 5;
|
||||
}
|
||||
.pcard-resize:hover { background: linear-gradient(135deg, transparent 50%, #58a6ff 50%, #58a6ff 60%, transparent 60%, transparent 70%, #58a6ff 70%, #58a6ff 80%, transparent 80%); }
|
||||
|
||||
/* Bottom hint bar */
|
||||
.hint-bar {
|
||||
position: fixed; bottom: 0; left: 0; right: 0;
|
||||
background: rgba(0,0,0,.6);
|
||||
backdrop-filter: blur(10px);
|
||||
border-top: 1px solid rgba(255,255,255,.08);
|
||||
padding: 6px 16px;
|
||||
font-size: 11px; color: #8b949e;
|
||||
display: flex; gap: 18px; align-items: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
.hint-bar kbd {
|
||||
background: rgba(255,255,255,.08);
|
||||
padding: 1px 6px; border-radius: 3px;
|
||||
font-family: "SF Mono", monospace;
|
||||
font-size: 10px;
|
||||
border: 1px solid rgba(255,255,255,.1);
|
||||
color: #e6edf3;
|
||||
}
|
||||
.hint-bar .reset-layout {
|
||||
margin-left: auto;
|
||||
background: transparent; border: 1px solid rgba(255,255,255,.15);
|
||||
color: #8b949e; padding: 3px 10px; border-radius: 4px;
|
||||
font-size: 11px; cursor: pointer; font-family: inherit;
|
||||
}
|
||||
.hint-bar .reset-layout:hover { background: rgba(248,81,73,.15); border-color: #f85149; color: #f85149; }
|
||||
|
||||
body.is-dragging-card * { user-select: none !important; }
|
||||
body.is-dragging-card iframe { pointer-events: none !important; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="stage">
|
||||
<div class="pcard pcard-preview" id="card-cur" style="--dot-color:#58a6ff">
|
||||
<div class="pcard-head" data-drag>
|
||||
<span class="pcard-dot"></span>
|
||||
<span class="pcard-title">CURRENT</span>
|
||||
<span class="pcard-meta" id="cur-meta">—</span>
|
||||
</div>
|
||||
<div class="pcard-body"><iframe id="iframe-cur"></iframe></div>
|
||||
<div class="pcard-resize" data-resize></div>
|
||||
</div>
|
||||
|
||||
<div class="pcard pcard-preview" id="card-nxt" style="--dot-color:#bc8cff">
|
||||
<div class="pcard-head" data-drag>
|
||||
<span class="pcard-dot"></span>
|
||||
<span class="pcard-title">NEXT</span>
|
||||
<span class="pcard-meta" id="nxt-meta">—</span>
|
||||
</div>
|
||||
<div class="pcard-body"><iframe id="iframe-nxt"></iframe></div>
|
||||
<div class="pcard-resize" data-resize></div>
|
||||
</div>
|
||||
|
||||
<div class="pcard pcard-notes" id="card-notes" style="--dot-color:#f0883e">
|
||||
<div class="pcard-head" data-drag>
|
||||
<span class="pcard-dot"></span>
|
||||
<span class="pcard-title">SPEAKER SCRIPT · 逐字稿</span>
|
||||
</div>
|
||||
<div class="pcard-body" id="notes-body"></div>
|
||||
<div class="pcard-resize" data-resize></div>
|
||||
</div>
|
||||
|
||||
<div class="pcard pcard-timer" id="card-timer" style="--dot-color:#3fb950">
|
||||
<div class="pcard-head" data-drag>
|
||||
<span class="pcard-dot"></span>
|
||||
<span class="pcard-title">TIMER</span>
|
||||
</div>
|
||||
<div class="pcard-body">
|
||||
<div class="timer-display" id="timer-display">00:00</div>
|
||||
<div class="timer-row">
|
||||
<span class="label">Slide</span>
|
||||
<span class="val" id="timer-count">1 / ${total}</span>
|
||||
</div>
|
||||
<div class="timer-controls">
|
||||
<button class="timer-btn" id="btn-prev">← Prev</button>
|
||||
<button class="timer-btn" id="btn-next">Next →</button>
|
||||
<button class="timer-btn" id="btn-reset">⏱ Reset</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pcard-resize" data-resize></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hint-bar">
|
||||
<span><kbd>← →</kbd> 翻页</span>
|
||||
<span><kbd>R</kbd> 重置计时</span>
|
||||
<span><kbd>Esc</kbd> 关闭</span>
|
||||
<span style="color:#6e7681">拖动卡片头部移动 · 拖动右下角调整大小</span>
|
||||
<button class="reset-layout" id="reset-layout">重置布局</button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function(){
|
||||
var slideMeta = ${metaJSON};
|
||||
var total = ${total};
|
||||
var idx = ${startIdx};
|
||||
var deckUrl = ${deckUrlJSON};
|
||||
var STORAGE_KEY = ${JSON.stringify(storageKey)};
|
||||
var bc;
|
||||
try { bc = new BroadcastChannel(${channelJSON}); } catch(e) {}
|
||||
|
||||
var iframeCur = document.getElementById('iframe-cur');
|
||||
var iframeNxt = document.getElementById('iframe-nxt');
|
||||
var notesBody = document.getElementById('notes-body');
|
||||
var curMeta = document.getElementById('cur-meta');
|
||||
var nxtMeta = document.getElementById('nxt-meta');
|
||||
var timerDisplay = document.getElementById('timer-display');
|
||||
var timerCount = document.getElementById('timer-count');
|
||||
|
||||
/* ===== Default card layout ===== */
|
||||
function defaultLayout() {
|
||||
var w = window.innerWidth;
|
||||
var h = window.innerHeight - 36; /* leave room for hint bar */
|
||||
return {
|
||||
'card-cur': { x: 16, y: 16, w: Math.round(w*0.55) - 24, h: Math.round(h*0.62) - 16 },
|
||||
'card-nxt': { x: Math.round(w*0.55) + 8, y: 16, w: w - Math.round(w*0.55) - 24, h: Math.round(h*0.42) - 16 },
|
||||
'card-notes': { x: Math.round(w*0.55) + 8, y: Math.round(h*0.42) + 8, w: w - Math.round(w*0.55) - 24, h: h - Math.round(h*0.42) - 16 },
|
||||
'card-timer': { x: 16, y: Math.round(h*0.62) + 8, w: Math.round(w*0.55) - 24, h: h - Math.round(h*0.62) - 16 }
|
||||
};
|
||||
}
|
||||
|
||||
/* ===== Apply / save / restore layout ===== */
|
||||
function applyLayout(layout) {
|
||||
Object.keys(layout).forEach(function(id){
|
||||
var el = document.getElementById(id);
|
||||
var l = layout[id];
|
||||
if (el && l) {
|
||||
el.style.left = l.x + 'px';
|
||||
el.style.top = l.y + 'px';
|
||||
el.style.width = l.w + 'px';
|
||||
el.style.height = l.h + 'px';
|
||||
}
|
||||
});
|
||||
rescaleAll();
|
||||
}
|
||||
function readLayout() {
|
||||
try {
|
||||
var saved = localStorage.getItem(STORAGE_KEY);
|
||||
if (saved) return JSON.parse(saved);
|
||||
} catch(e) {}
|
||||
return defaultLayout();
|
||||
}
|
||||
function saveLayout() {
|
||||
var layout = {};
|
||||
['card-cur','card-nxt','card-notes','card-timer'].forEach(function(id){
|
||||
var el = document.getElementById(id);
|
||||
if (el) {
|
||||
layout[id] = {
|
||||
x: parseInt(el.style.left,10) || 0,
|
||||
y: parseInt(el.style.top,10) || 0,
|
||||
w: parseInt(el.style.width,10) || 300,
|
||||
h: parseInt(el.style.height,10) || 200
|
||||
};
|
||||
}
|
||||
});
|
||||
try { localStorage.setItem(STORAGE_KEY, JSON.stringify(layout)); } catch(e) {}
|
||||
}
|
||||
|
||||
/* ===== iframe rescale to fit card body ===== */
|
||||
function rescaleIframe(iframe) {
|
||||
if (!iframe || iframe.style.display === 'none') return;
|
||||
var body = iframe.parentElement;
|
||||
var cw = body.clientWidth, ch = body.clientHeight;
|
||||
if (!cw || !ch) return;
|
||||
var s = Math.min(cw / 1920, ch / 1080);
|
||||
iframe.style.transform = 'scale(' + s + ')';
|
||||
/* Center the scaled iframe in the body */
|
||||
var sw = 1920 * s, sh = 1080 * s;
|
||||
iframe.style.left = Math.max(0, (cw - sw) / 2) + 'px';
|
||||
iframe.style.top = Math.max(0, (ch - sh) / 2) + 'px';
|
||||
}
|
||||
function rescaleAll() {
|
||||
rescaleIframe(iframeCur);
|
||||
rescaleIframe(iframeNxt);
|
||||
}
|
||||
window.addEventListener('resize', rescaleAll);
|
||||
|
||||
/* ===== Drag (move card by header) ===== */
|
||||
document.querySelectorAll('[data-drag]').forEach(function(handle){
|
||||
handle.addEventListener('mousedown', function(e){
|
||||
if (e.button !== 0) return;
|
||||
var card = handle.closest('.pcard');
|
||||
if (!card) return;
|
||||
e.preventDefault();
|
||||
card.classList.add('dragging');
|
||||
document.body.classList.add('is-dragging-card');
|
||||
var startX = e.clientX, startY = e.clientY;
|
||||
var startL = parseInt(card.style.left,10) || 0;
|
||||
var startT = parseInt(card.style.top,10) || 0;
|
||||
function onMove(ev){
|
||||
var nx = Math.max(0, Math.min(window.innerWidth - 100, startL + ev.clientX - startX));
|
||||
var ny = Math.max(0, Math.min(window.innerHeight - 50, startT + ev.clientY - startY));
|
||||
card.style.left = nx + 'px';
|
||||
card.style.top = ny + 'px';
|
||||
}
|
||||
function onUp(){
|
||||
card.classList.remove('dragging');
|
||||
document.body.classList.remove('is-dragging-card');
|
||||
document.removeEventListener('mousemove', onMove);
|
||||
document.removeEventListener('mouseup', onUp);
|
||||
saveLayout();
|
||||
}
|
||||
document.addEventListener('mousemove', onMove);
|
||||
document.addEventListener('mouseup', onUp);
|
||||
});
|
||||
});
|
||||
|
||||
/* ===== Resize (drag bottom-right corner) ===== */
|
||||
document.querySelectorAll('[data-resize]').forEach(function(handle){
|
||||
handle.addEventListener('mousedown', function(e){
|
||||
if (e.button !== 0) return;
|
||||
var card = handle.closest('.pcard');
|
||||
if (!card) return;
|
||||
e.preventDefault(); e.stopPropagation();
|
||||
card.classList.add('resizing');
|
||||
document.body.classList.add('is-dragging-card');
|
||||
var startX = e.clientX, startY = e.clientY;
|
||||
var startW = parseInt(card.style.width,10) || card.offsetWidth;
|
||||
var startH = parseInt(card.style.height,10) || card.offsetHeight;
|
||||
function onMove(ev){
|
||||
var nw = Math.max(180, startW + ev.clientX - startX);
|
||||
var nh = Math.max(100, startH + ev.clientY - startY);
|
||||
card.style.width = nw + 'px';
|
||||
card.style.height = nh + 'px';
|
||||
if (card.querySelector('iframe')) rescaleIframe(card.querySelector('iframe'));
|
||||
}
|
||||
function onUp(){
|
||||
card.classList.remove('resizing');
|
||||
document.body.classList.remove('is-dragging-card');
|
||||
document.removeEventListener('mousemove', onMove);
|
||||
document.removeEventListener('mouseup', onUp);
|
||||
rescaleAll();
|
||||
saveLayout();
|
||||
}
|
||||
document.addEventListener('mousemove', onMove);
|
||||
document.addEventListener('mouseup', onUp);
|
||||
});
|
||||
});
|
||||
|
||||
/* ===== Preview iframe ready tracking =====
|
||||
* Each iframe loads the deck ONCE with ?preview=1 on init. Subsequent
|
||||
* slide changes are sent via postMessage('preview-goto') so the iframe
|
||||
* just toggles visibility of a different .slide — no reload, no flicker.
|
||||
*/
|
||||
var iframeReady = { cur: false, nxt: false };
|
||||
var currentTheme = ${themeJSON};
|
||||
window.addEventListener('message', function(e) {
|
||||
if (!e.data || e.data.type !== 'preview-ready') return;
|
||||
var iframe = null;
|
||||
if (e.source === iframeCur.contentWindow) {
|
||||
iframeReady.cur = true;
|
||||
iframe = iframeCur;
|
||||
postPreviewGoto(iframeCur, idx);
|
||||
} else if (e.source === iframeNxt.contentWindow) {
|
||||
iframeReady.nxt = true;
|
||||
iframe = iframeNxt;
|
||||
postPreviewGoto(iframeNxt, idx + 1 < total ? idx + 1 : idx);
|
||||
}
|
||||
/* Sync current theme to the iframe */
|
||||
if (iframe && currentTheme) {
|
||||
try { iframe.contentWindow.postMessage({ type: 'preview-theme', name: currentTheme }, '*'); } catch(err) {}
|
||||
}
|
||||
if (iframe) rescaleIframe(iframe);
|
||||
});
|
||||
|
||||
function postPreviewGoto(iframe, n) {
|
||||
try {
|
||||
iframe.contentWindow.postMessage({ type: 'preview-goto', idx: n }, '*');
|
||||
} catch(e) {}
|
||||
}
|
||||
|
||||
/* ===== Update content =====
|
||||
* Smooth (no-reload) navigation: send postMessage to iframes instead of
|
||||
* resetting src. Iframes stay loaded, just switch visible .slide.
|
||||
*/
|
||||
function update(n) {
|
||||
n = Math.max(0, Math.min(total - 1, n));
|
||||
idx = n;
|
||||
|
||||
/* Current preview — postMessage (smooth) */
|
||||
if (iframeReady.cur) postPreviewGoto(iframeCur, n);
|
||||
curMeta.textContent = (n + 1) + '/' + total;
|
||||
|
||||
/* Next preview */
|
||||
if (n + 1 < total) {
|
||||
iframeNxt.style.display = '';
|
||||
var endEl = document.querySelector('#card-nxt .preview-end');
|
||||
if (endEl) endEl.remove();
|
||||
if (iframeReady.nxt) postPreviewGoto(iframeNxt, n + 1);
|
||||
nxtMeta.textContent = (n + 2) + '/' + total;
|
||||
} else {
|
||||
iframeNxt.style.display = 'none';
|
||||
var body = document.querySelector('#card-nxt .pcard-body');
|
||||
if (body && !body.querySelector('.preview-end')) {
|
||||
var end = document.createElement('div');
|
||||
end.className = 'preview-end';
|
||||
end.textContent = '— END OF DECK —';
|
||||
body.appendChild(end);
|
||||
}
|
||||
nxtMeta.textContent = 'END';
|
||||
}
|
||||
|
||||
/* Notes */
|
||||
var note = slideMeta[n].notes;
|
||||
notesBody.innerHTML = note || '<span class="empty">(这一页还没有逐字稿)</span>';
|
||||
|
||||
/* Timer count */
|
||||
timerCount.textContent = (n + 1) + ' / ' + total;
|
||||
}
|
||||
|
||||
/* ===== Timer ===== */
|
||||
var tStart = Date.now();
|
||||
setInterval(function(){
|
||||
var s = Math.floor((Date.now() - tStart) / 1000);
|
||||
var mm = String(Math.floor(s/60)).padStart(2,'0');
|
||||
var ss = String(s%60).padStart(2,'0');
|
||||
timerDisplay.textContent = mm + ':' + ss;
|
||||
}, 1000);
|
||||
function resetTimer(){ tStart = Date.now(); timerDisplay.textContent = '00:00'; }
|
||||
|
||||
/* ===== BroadcastChannel sync ===== */
|
||||
if (bc) {
|
||||
bc.onmessage = function(e){
|
||||
if (!e.data) return;
|
||||
if (e.data.type === 'go') update(e.data.idx);
|
||||
else if (e.data.type === 'theme' && e.data.name) {
|
||||
currentTheme = e.data.name;
|
||||
/* Forward theme change to preview iframes */
|
||||
[iframeCur, iframeNxt].forEach(function(iframe){
|
||||
try {
|
||||
iframe.contentWindow.postMessage({ type: 'preview-theme', name: e.data.name }, '*');
|
||||
} catch(err) {}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
function go(n) {
|
||||
update(n);
|
||||
if (bc) bc.postMessage({ type: 'go', idx: idx });
|
||||
}
|
||||
|
||||
/* ===== Buttons ===== */
|
||||
document.getElementById('btn-prev').addEventListener('click', function(){ go(idx - 1); });
|
||||
document.getElementById('btn-next').addEventListener('click', function(){ go(idx + 1); });
|
||||
document.getElementById('btn-reset').addEventListener('click', resetTimer);
|
||||
document.getElementById('reset-layout').addEventListener('click', function(){
|
||||
if (confirm('恢复默认卡片布局?')) {
|
||||
try { localStorage.removeItem(STORAGE_KEY); } catch(e){}
|
||||
applyLayout(defaultLayout());
|
||||
}
|
||||
});
|
||||
|
||||
/* ===== Keyboard ===== */
|
||||
document.addEventListener('keydown', function(e){
|
||||
if (e.metaKey || e.ctrlKey || e.altKey) return;
|
||||
switch(e.key) {
|
||||
case 'ArrowRight': case ' ': case 'PageDown': go(idx + 1); e.preventDefault(); break;
|
||||
case 'ArrowLeft': case 'PageUp': go(idx - 1); e.preventDefault(); break;
|
||||
case 'Home': go(0); break;
|
||||
case 'End': go(total - 1); break;
|
||||
case 'r': case 'R': resetTimer(); break;
|
||||
case 'Escape': window.close(); break;
|
||||
}
|
||||
});
|
||||
|
||||
/* ===== Iframe load → rescale (catches initial size) ===== */
|
||||
iframeCur.addEventListener('load', function(){ rescaleIframe(iframeCur); });
|
||||
iframeNxt.addEventListener('load', function(){ rescaleIframe(iframeNxt); });
|
||||
|
||||
/* ===== Init =====
|
||||
* Load each iframe ONCE with the deck file. After they post
|
||||
* 'preview-ready', all subsequent navigation is via postMessage
|
||||
* (smooth, no reload, no flicker).
|
||||
*/
|
||||
applyLayout(readLayout());
|
||||
iframeCur.src = deckUrl + '?preview=' + (idx + 1);
|
||||
if (idx + 1 < total) iframeNxt.src = deckUrl + '?preview=' + (idx + 2);
|
||||
/* Initialize notes/timer/count without touching iframes */
|
||||
notesBody.innerHTML = slideMeta[idx].notes || '<span class="empty">(这一页还没有逐字稿)</span>';
|
||||
curMeta.textContent = (idx + 1) + '/' + total;
|
||||
nxtMeta.textContent = (idx + 2) + '/' + total;
|
||||
timerCount.textContent = (idx + 1) + ' / ' + total;
|
||||
})();
|
||||
</` + `script>
|
||||
</body></html>`;
|
||||
}
|
||||
|
||||
function fullscreen(){ const el=document.documentElement;
|
||||
if (!document.fullscreenElement) el.requestFullscreen&&el.requestFullscreen();
|
||||
else document.exitFullscreen&&document.exitFullscreen();
|
||||
}
|
||||
|
||||
// theme cycling
|
||||
const root = document.documentElement;
|
||||
const themesAttr = root.getAttribute('data-themes') || document.body.getAttribute('data-themes');
|
||||
const themes = themesAttr ? themesAttr.split(',').map(s=>s.trim()).filter(Boolean) : [];
|
||||
let themeIdx = 0;
|
||||
|
||||
// Auto-detect theme base path from existing <link id="theme-link">
|
||||
let themeBase = root.getAttribute('data-theme-base');
|
||||
if (!themeBase) {
|
||||
const existingLink = document.getElementById('theme-link');
|
||||
if (existingLink) {
|
||||
// el.getAttribute('href') gives the raw relative path written in HTML
|
||||
const rawHref = existingLink.getAttribute('href') || '';
|
||||
const lastSlash = rawHref.lastIndexOf('/');
|
||||
themeBase = lastSlash >= 0 ? rawHref.substring(0, lastSlash + 1) : 'assets/themes/';
|
||||
} else {
|
||||
themeBase = 'assets/themes/';
|
||||
}
|
||||
}
|
||||
|
||||
function applyTheme(name) {
|
||||
let link = document.getElementById('theme-link');
|
||||
if (!link) {
|
||||
link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.id = 'theme-link';
|
||||
document.head.appendChild(link);
|
||||
}
|
||||
link.href = themeBase + name + '.css';
|
||||
root.setAttribute('data-theme', name);
|
||||
const ind = document.querySelector('.theme-indicator');
|
||||
if (ind) ind.textContent = name;
|
||||
}
|
||||
function cycleTheme(fromRemote){
|
||||
if (!themes.length) return;
|
||||
themeIdx = (themeIdx+1) % themes.length;
|
||||
const name = themes[themeIdx];
|
||||
applyTheme(name);
|
||||
/* Broadcast to other window (audience ↔ presenter) */
|
||||
if (!fromRemote && bc) bc.postMessage({ type: 'theme', name: name });
|
||||
}
|
||||
|
||||
// animation cycling on current slide
|
||||
let animIdx = 0;
|
||||
function cycleAnim(){
|
||||
animIdx = (animIdx+1) % ANIMS.length;
|
||||
const a = ANIMS[animIdx];
|
||||
const target = slides[idx].querySelector('[data-anim-target]') || slides[idx];
|
||||
ANIMS.forEach(x => target.classList.remove('anim-'+x));
|
||||
void target.offsetWidth;
|
||||
target.classList.add('anim-'+a);
|
||||
target.setAttribute('data-anim', a);
|
||||
const ind = document.querySelector('.anim-indicator');
|
||||
if (ind) ind.textContent = a;
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', function (e) {
|
||||
if (e.metaKey||e.ctrlKey||e.altKey) return;
|
||||
switch (e.key) {
|
||||
case 'ArrowRight': case ' ': case 'PageDown': case 'Enter': go(idx+1); e.preventDefault(); break;
|
||||
case 'ArrowLeft': case 'PageUp': case 'Backspace': go(idx-1); e.preventDefault(); break;
|
||||
case 'Home': go(0); break;
|
||||
case 'End': go(total-1); break;
|
||||
case 'f': case 'F': fullscreen(); break;
|
||||
case 's': case 'S': openPresenterWindow(); break;
|
||||
case 'n': case 'N': toggleNotes(); break;
|
||||
case 'o': case 'O': toggleOverview(); break;
|
||||
case 't': case 'T': cycleTheme(); break;
|
||||
case 'a': case 'A': cycleAnim(); break;
|
||||
case 'Escape': toggleOverview(false); toggleNotes(false); break;
|
||||
}
|
||||
});
|
||||
|
||||
// hash deep-link
|
||||
function fromHash(){
|
||||
const m = /^#\/(\d+)/.exec(location.hash||'');
|
||||
if (m) go(Math.max(0, parseInt(m[1],10)-1));
|
||||
}
|
||||
window.addEventListener('hashchange', fromHash);
|
||||
fromHash();
|
||||
go(idx);
|
||||
});
|
||||
})();
|
||||
23
skills/html-ppt/assets/themes/academic-paper.css
Normal file
23
skills/html-ppt/assets/themes/academic-paper.css
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
/* theme: academic-paper — 学术论文 */
|
||||
:root{
|
||||
--bg:#fdfcf8;--bg-soft:#f7f5ed;--surface:#ffffff;--surface-2:#f5f3ea;
|
||||
--border:rgba(20,20,20,.14);--border-strong:rgba(20,20,20,.35);
|
||||
--text-1:#0a0a0a;--text-2:#333333;--text-3:#707070;
|
||||
--accent:#1a3a7a;--accent-2:#0a0a0a;--accent-3:#8a1a1a;
|
||||
--good:#1a5a2a;--warn:#8a6a1a;--bad:#8a1a1a;
|
||||
--grad:linear-gradient(135deg,#1a3a7a,#0a0a0a);
|
||||
--grad-soft:linear-gradient(135deg,#e8edf8,#f5f3ea);
|
||||
--radius:0px;--radius-sm:0px;--radius-lg:0px;
|
||||
--shadow:none;
|
||||
--shadow-lg:0 1px 2px rgba(0,0,0,.1);
|
||||
--font-sans:'Latin Modern Roman','Playfair Display','Noto Serif SC',Georgia,serif;
|
||||
--font-serif:'Latin Modern Roman','Playfair Display','Noto Serif SC',Georgia,serif;
|
||||
--font-display:'Latin Modern Roman','Playfair Display','Noto Serif SC',Georgia,serif;
|
||||
}
|
||||
body{font-family:var(--font-serif)}
|
||||
h1.title,h2.title,.h1,.h2{font-weight:700;font-family:var(--font-serif)}
|
||||
.card{border:1px solid var(--border);box-shadow:none}
|
||||
.divider{background:var(--text-1);height:1px}
|
||||
.divider-accent{background:var(--text-1);height:2px;width:100%}
|
||||
a{color:var(--accent);text-decoration:underline}
|
||||
.kicker{color:var(--accent);font-style:italic;text-transform:none;letter-spacing:0;font-weight:400}
|
||||
14
skills/html-ppt/assets/themes/arctic-cool.css
Normal file
14
skills/html-ppt/assets/themes/arctic-cool.css
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
/* theme: arctic-cool — 冷色调 蓝/青/石板灰 */
|
||||
:root{
|
||||
--bg:#f2f6fb;--bg-soft:#e7eef7;--surface:#ffffff;--surface-2:#edf3fa;
|
||||
--border:rgba(40,70,110,.12);--border-strong:rgba(40,70,110,.24);
|
||||
--text-1:#0e1f33;--text-2:#3a5778;--text-3:#6b819b;
|
||||
--accent:#1e6fb0;--accent-2:#17b1b1;--accent-3:#6f8aa6;
|
||||
--good:#1aaf84;--warn:#d19030;--bad:#c5485a;
|
||||
--grad:linear-gradient(135deg,#1e6fb0,#17b1b1 60%,#5fb9d6);
|
||||
--grad-soft:linear-gradient(135deg,#e7eef7,#dff3f3);
|
||||
--radius:14px;--radius-sm:10px;--radius-lg:22px;
|
||||
--shadow:0 10px 28px rgba(40,70,110,.12);
|
||||
--shadow-lg:0 24px 60px rgba(40,70,110,.18);
|
||||
--font-sans:'Inter','Noto Sans SC',sans-serif;
|
||||
}
|
||||
20
skills/html-ppt/assets/themes/aurora.css
Normal file
20
skills/html-ppt/assets/themes/aurora.css
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
/* theme: aurora — 极光渐变 */
|
||||
:root{
|
||||
--bg:#06091c;--bg-soft:#0a1130;--surface:rgba(255,255,255,.05);--surface-2:rgba(255,255,255,.08);
|
||||
--border:rgba(180,220,255,.14);--border-strong:rgba(180,220,255,.28);
|
||||
--text-1:#e8f0ff;--text-2:#b4c4e4;--text-3:#6a7a9e;
|
||||
--accent:#5ef2c6;--accent-2:#7aa2ff;--accent-3:#c984ff;
|
||||
--good:#5ef2c6;--warn:#ffd27a;--bad:#ff8ab0;
|
||||
--grad:linear-gradient(135deg,#5ef2c6,#7aa2ff 50%,#c984ff);
|
||||
--grad-soft:linear-gradient(135deg,rgba(94,242,198,.2),rgba(201,132,255,.2));
|
||||
--radius:20px;--radius-sm:14px;--radius-lg:28px;
|
||||
--shadow:0 20px 60px rgba(0,0,0,.4),inset 0 1px 0 rgba(255,255,255,.08);
|
||||
--shadow-lg:0 30px 80px rgba(0,0,0,.55);
|
||||
--font-sans:'Inter','Noto Sans SC',sans-serif;
|
||||
}
|
||||
body{background:
|
||||
radial-gradient(60% 50% at 20% 10%,rgba(94,242,198,.35),transparent 70%),
|
||||
radial-gradient(55% 50% at 80% 20%,rgba(122,162,255,.32),transparent 70%),
|
||||
radial-gradient(70% 60% at 50% 100%,rgba(201,132,255,.3),transparent 70%),
|
||||
#06091c}
|
||||
.card{backdrop-filter:blur(24px) saturate(160%);-webkit-backdrop-filter:blur(24px) saturate(160%)}
|
||||
16
skills/html-ppt/assets/themes/bauhaus.css
Normal file
16
skills/html-ppt/assets/themes/bauhaus.css
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
/* theme: bauhaus — 几何+原色 */
|
||||
:root{
|
||||
--bg:#f4efe3;--bg-soft:#e8e2d1;--surface:#ffffff;--surface-2:#f4efe3;
|
||||
--border:#111111;--border-strong:#111111;
|
||||
--text-1:#111111;--text-2:#333333;--text-3:#666666;
|
||||
--accent:#e03c27;--accent-2:#f4c430;--accent-3:#1d4eaf;
|
||||
--good:#1b8c3c;--warn:#f4c430;--bad:#e03c27;
|
||||
--grad:linear-gradient(135deg,#e03c27 0 33%,#f4c430 33% 66%,#1d4eaf 66% 100%);
|
||||
--grad-soft:linear-gradient(135deg,#f4efe3,#e8e2d1);
|
||||
--radius:0;--radius-sm:0;--radius-lg:0;
|
||||
--shadow:4px 4px 0 #111;--shadow-lg:8px 8px 0 #111;
|
||||
--font-sans:'Space Grotesk','Inter','Noto Sans SC',sans-serif;
|
||||
--font-display:'Archivo Black',sans-serif;
|
||||
--letter-tight:-.03em;
|
||||
}
|
||||
.card{border:2px solid #111}
|
||||
19
skills/html-ppt/assets/themes/blueprint.css
Normal file
19
skills/html-ppt/assets/themes/blueprint.css
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
/* theme: blueprint — 蓝图工程 */
|
||||
:root{
|
||||
--bg:#0b3a6f;--bg-soft:#0a3260;--surface:rgba(255,255,255,.06);--surface-2:rgba(255,255,255,.1);
|
||||
--border:rgba(190,220,255,.3);--border-strong:rgba(190,220,255,.55);
|
||||
--text-1:#e8f3ff;--text-2:#b8d4f0;--text-3:#7da8cf;
|
||||
--accent:#ffffff;--accent-2:#aee1ff;--accent-3:#ffd27a;
|
||||
--good:#8ef0a6;--warn:#ffd27a;--bad:#ff8a96;
|
||||
--grad:linear-gradient(135deg,#ffffff,#aee1ff);
|
||||
--grad-soft:linear-gradient(135deg,#0a3260,#0b3a6f);
|
||||
--radius:2px;--radius-sm:2px;--radius-lg:4px;
|
||||
--shadow:none;--shadow-lg:0 16px 40px rgba(0,0,0,.3);
|
||||
--font-sans:'JetBrains Mono','IBM Plex Mono',monospace;
|
||||
--font-display:'JetBrains Mono',monospace;
|
||||
}
|
||||
body{background:
|
||||
linear-gradient(rgba(255,255,255,.06) 1px,transparent 1px) 0 0/40px 40px,
|
||||
linear-gradient(90deg,rgba(255,255,255,.06) 1px,transparent 1px) 0 0/40px 40px,
|
||||
#0b3a6f}
|
||||
.card{border:1px dashed rgba(190,220,255,.45);background:rgba(255,255,255,.04)}
|
||||
14
skills/html-ppt/assets/themes/catppuccin-latte.css
Normal file
14
skills/html-ppt/assets/themes/catppuccin-latte.css
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
/* theme: catppuccin-latte — catppuccin 浅 */
|
||||
:root{
|
||||
--bg:#eff1f5;--bg-soft:#e6e9ef;--surface:#ffffff;--surface-2:#eef0f4;
|
||||
--border:rgba(76,79,105,.14);--border-strong:rgba(76,79,105,.28);
|
||||
--text-1:#4c4f69;--text-2:#6c6f85;--text-3:#9ca0b0;
|
||||
--accent:#8839ef;--accent-2:#1e66f5;--accent-3:#ea76cb;
|
||||
--good:#40a02b;--warn:#df8e1d;--bad:#d20f39;
|
||||
--grad:linear-gradient(135deg,#8839ef,#1e66f5 50%,#04a5e5);
|
||||
--grad-soft:linear-gradient(135deg,#eff1f5,#e6e9ef);
|
||||
--radius:14px;--radius-sm:10px;--radius-lg:22px;
|
||||
--shadow:0 8px 24px rgba(76,79,105,.1);
|
||||
--shadow-lg:0 20px 56px rgba(76,79,105,.16);
|
||||
--font-sans:'Inter','Noto Sans SC',sans-serif;
|
||||
}
|
||||
14
skills/html-ppt/assets/themes/catppuccin-mocha.css
Normal file
14
skills/html-ppt/assets/themes/catppuccin-mocha.css
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
/* theme: catppuccin-mocha — catppuccin 深 */
|
||||
:root{
|
||||
--bg:#1e1e2e;--bg-soft:#181825;--surface:#313244;--surface-2:#45475a;
|
||||
--border:rgba(205,214,244,.12);--border-strong:rgba(205,214,244,.24);
|
||||
--text-1:#cdd6f4;--text-2:#a6adc8;--text-3:#7f849c;
|
||||
--accent:#cba6f7;--accent-2:#89b4fa;--accent-3:#f5c2e7;
|
||||
--good:#a6e3a1;--warn:#f9e2af;--bad:#f38ba8;
|
||||
--grad:linear-gradient(135deg,#cba6f7,#89b4fa 50%,#94e2d5);
|
||||
--grad-soft:linear-gradient(135deg,#313244,#45475a);
|
||||
--radius:14px;--radius-sm:10px;--radius-lg:22px;
|
||||
--shadow:0 10px 30px rgba(0,0,0,.35);
|
||||
--shadow-lg:0 24px 60px rgba(0,0,0,.5);
|
||||
--font-sans:'Inter','Noto Sans SC',sans-serif;
|
||||
}
|
||||
19
skills/html-ppt/assets/themes/corporate-clean.css
Normal file
19
skills/html-ppt/assets/themes/corporate-clean.css
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
/* theme: corporate-clean — 企业商务 */
|
||||
:root{
|
||||
--bg:#ffffff;--bg-soft:#f5f7fa;--surface:#ffffff;--surface-2:#f0f3f7;
|
||||
--border:rgba(10,37,64,.12);--border-strong:rgba(10,37,64,.28);
|
||||
--text-1:#0a2540;--text-2:#425466;--text-3:#8898aa;
|
||||
--accent:#0a2540;--accent-2:#1d4ed8;--accent-3:#64748b;
|
||||
--good:#0e9f6e;--warn:#d97706;--bad:#dc2626;
|
||||
--grad:linear-gradient(135deg,#0a2540,#1d4ed8);
|
||||
--grad-soft:linear-gradient(135deg,#f0f4fb,#e4ecf7);
|
||||
--radius:6px;--radius-sm:4px;--radius-lg:10px;
|
||||
--shadow:0 1px 3px rgba(10,37,64,.08),0 4px 12px rgba(10,37,64,.05);
|
||||
--shadow-lg:0 4px 12px rgba(10,37,64,.1),0 16px 40px rgba(10,37,64,.08);
|
||||
--font-sans:'Inter','Noto Sans SC',sans-serif;
|
||||
--font-display:'Inter','Noto Sans SC',sans-serif;
|
||||
}
|
||||
.card{border:1px solid var(--border)}
|
||||
.divider-accent{background:var(--accent);height:3px;width:56px}
|
||||
.kicker{color:var(--accent-2)}
|
||||
h1.title,h2.title,.h1,.h2{font-weight:700;color:var(--accent)}
|
||||
23
skills/html-ppt/assets/themes/cyberpunk-neon.css
Normal file
23
skills/html-ppt/assets/themes/cyberpunk-neon.css
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
/* theme: cyberpunk-neon — 赛博朋克霓虹 */
|
||||
:root{
|
||||
--bg:#000000;--bg-soft:#0a0a12;--surface:#0f0f1a;--surface-2:#14141f;
|
||||
--border:rgba(255,0,170,.25);--border-strong:rgba(0,240,255,.55);
|
||||
--text-1:#f5f7ff;--text-2:#b4b8d4;--text-3:#6b6e8a;
|
||||
--accent:#ff2bd6;--accent-2:#00f0ff;--accent-3:#f9f871;
|
||||
--good:#39ff14;--warn:#f9f871;--bad:#ff2bd6;
|
||||
--grad:linear-gradient(135deg,#ff2bd6,#7a00ff 50%,#00f0ff);
|
||||
--grad-soft:linear-gradient(135deg,rgba(255,43,214,.18),rgba(0,240,255,.18));
|
||||
--radius:6px;--radius-sm:3px;--radius-lg:10px;
|
||||
--shadow:0 0 0 1px rgba(255,43,214,.35),0 0 24px rgba(255,43,214,.35),0 0 48px rgba(0,240,255,.18);
|
||||
--shadow-lg:0 0 0 1px rgba(0,240,255,.5),0 0 40px rgba(0,240,255,.45),0 0 80px rgba(255,43,214,.3);
|
||||
--font-sans:'Inter','Noto Sans SC',sans-serif;
|
||||
--font-display:'JetBrains Mono','IBM Plex Mono',monospace;
|
||||
}
|
||||
body{background:
|
||||
radial-gradient(ellipse at 15% 0%,rgba(255,43,214,.22),transparent 60%),
|
||||
radial-gradient(ellipse at 85% 100%,rgba(0,240,255,.2),transparent 60%),
|
||||
#000}
|
||||
h1.title,h2.title,.h1,.h2{text-shadow:0 0 12px rgba(255,43,214,.6),0 0 30px rgba(0,240,255,.35)}
|
||||
.kicker{color:var(--accent-2);text-shadow:0 0 8px rgba(0,240,255,.6)}
|
||||
.card{background:rgba(15,15,26,.72);backdrop-filter:blur(8px)}
|
||||
.divider-accent{background:var(--grad);box-shadow:0 0 12px var(--accent)}
|
||||
14
skills/html-ppt/assets/themes/dracula.css
Normal file
14
skills/html-ppt/assets/themes/dracula.css
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
/* theme: dracula — dracula 深色 */
|
||||
:root{
|
||||
--bg:#282a36;--bg-soft:#21222c;--surface:#343746;--surface-2:#44475a;
|
||||
--border:rgba(248,248,242,.12);--border-strong:rgba(248,248,242,.24);
|
||||
--text-1:#f8f8f2;--text-2:#bdbde0;--text-3:#6272a4;
|
||||
--accent:#bd93f9;--accent-2:#ff79c6;--accent-3:#8be9fd;
|
||||
--good:#50fa7b;--warn:#f1fa8c;--bad:#ff5555;
|
||||
--grad:linear-gradient(135deg,#bd93f9,#ff79c6 55%,#8be9fd);
|
||||
--grad-soft:linear-gradient(135deg,#343746,#44475a);
|
||||
--radius:12px;--radius-sm:8px;--radius-lg:18px;
|
||||
--shadow:0 10px 30px rgba(0,0,0,.4);
|
||||
--shadow-lg:0 22px 60px rgba(0,0,0,.55);
|
||||
--font-sans:'Inter','Noto Sans SC',sans-serif;
|
||||
}
|
||||
18
skills/html-ppt/assets/themes/editorial-serif.css
Normal file
18
skills/html-ppt/assets/themes/editorial-serif.css
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
/* theme: editorial-serif — 杂志风衬线,高级 */
|
||||
:root{
|
||||
--bg:#faf7f2;--bg-soft:#f3efe6;--surface:#ffffff;--surface-2:#f7f2e8;
|
||||
--border:rgba(40,28,18,.12);--border-strong:rgba(40,28,18,.24);
|
||||
--text-1:#1b1410;--text-2:#5c4a3e;--text-3:#8a7868;
|
||||
--accent:#8a2a1c;--accent-2:#c97a4a;--accent-3:#1b1410;
|
||||
--good:#3f7d4f;--warn:#b07a1f;--bad:#8a2a1c;
|
||||
--grad:linear-gradient(135deg,#8a2a1c,#c97a4a);
|
||||
--grad-soft:linear-gradient(135deg,#faf7f2,#f3efe6);
|
||||
--radius:4px;--radius-sm:2px;--radius-lg:8px;
|
||||
--shadow:0 2px 12px rgba(40,28,18,.06);
|
||||
--shadow-lg:0 20px 50px rgba(40,28,18,.14);
|
||||
--font-sans:'Playfair Display','Noto Serif SC',serif;
|
||||
--font-display:'Playfair Display','Noto Serif SC',serif;
|
||||
--font-serif:'Playfair Display','Noto Serif SC',serif;
|
||||
--letter-tight:-.02em;
|
||||
}
|
||||
.h1,.h2,h1.title,h2.title{font-style:italic;font-weight:600}
|
||||
26
skills/html-ppt/assets/themes/engineering-whiteprint.css
Normal file
26
skills/html-ppt/assets/themes/engineering-whiteprint.css
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
/* theme: engineering-whiteprint — 工程白图 */
|
||||
:root{
|
||||
--bg:#ffffff;--bg-soft:#f8fafc;--surface:#ffffff;--surface-2:#f4f7fb;
|
||||
--border:rgba(10,30,70,.22);--border-strong:#0a1e46;
|
||||
--text-1:#0a1e46;--text-2:#3a4a6a;--text-3:#8090a8;
|
||||
--accent:#0a1e46;--accent-2:#1e5ac4;--accent-3:#c42a10;
|
||||
--good:#1a6a3a;--warn:#c47a10;--bad:#c42a10;
|
||||
--grad:linear-gradient(135deg,#0a1e46,#1e5ac4);
|
||||
--grad-soft:linear-gradient(135deg,#eaf0fb,#f4f7fb);
|
||||
--radius:0px;--radius-sm:0px;--radius-lg:0px;
|
||||
--shadow:none;
|
||||
--shadow-lg:0 0 0 1px var(--border-strong);
|
||||
--font-sans:'Inter','Noto Sans SC',sans-serif;
|
||||
--font-mono:'JetBrains Mono','IBM Plex Mono',monospace;
|
||||
--font-display:'JetBrains Mono','Inter',monospace;
|
||||
}
|
||||
body{background:
|
||||
repeating-linear-gradient(0deg,rgba(10,30,70,.07) 0 1px,transparent 1px 40px),
|
||||
repeating-linear-gradient(90deg,rgba(10,30,70,.07) 0 1px,transparent 1px 40px),
|
||||
#ffffff}
|
||||
.card{border:1px solid var(--border-strong);box-shadow:none;background:rgba(255,255,255,.85)}
|
||||
.divider{background:var(--border-strong);height:1px}
|
||||
.divider-accent{background:var(--border-strong);height:1px;width:100%}
|
||||
.kicker{font-family:var(--font-mono);color:var(--accent-2);letter-spacing:.18em}
|
||||
h1.title,h2.title,.h1,.h2{font-weight:600}
|
||||
.pill{font-family:var(--font-mono);border:1px solid var(--border-strong);border-radius:0}
|
||||
21
skills/html-ppt/assets/themes/glassmorphism.css
Normal file
21
skills/html-ppt/assets/themes/glassmorphism.css
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
/* theme: glassmorphism — 毛玻璃 */
|
||||
:root{
|
||||
--bg:#0b1024;--bg-soft:#0e1530;--surface:rgba(255,255,255,.06);--surface-2:rgba(255,255,255,.1);
|
||||
--border:rgba(255,255,255,.14);--border-strong:rgba(255,255,255,.28);
|
||||
--text-1:#f2f4ff;--text-2:#c3c8e6;--text-3:#8287a8;
|
||||
--accent:#7dd3fc;--accent-2:#c084fc;--accent-3:#f0abfc;
|
||||
--good:#86efac;--warn:#fde68a;--bad:#fca5a5;
|
||||
--grad:linear-gradient(135deg,#7dd3fc,#c084fc 55%,#f0abfc);
|
||||
--grad-soft:linear-gradient(135deg,rgba(125,211,252,.18),rgba(192,132,252,.18));
|
||||
--radius:22px;--radius-sm:14px;--radius-lg:30px;
|
||||
--shadow:0 20px 60px rgba(0,0,0,.35),inset 0 1px 0 rgba(255,255,255,.12);
|
||||
--shadow-lg:0 30px 80px rgba(0,0,0,.5);
|
||||
--font-sans:'Inter','Noto Sans SC',sans-serif;
|
||||
}
|
||||
body{background:
|
||||
radial-gradient(60% 60% at 20% 20%,rgba(125,211,252,.3),transparent 60%),
|
||||
radial-gradient(50% 50% at 80% 30%,rgba(192,132,252,.28),transparent 60%),
|
||||
radial-gradient(60% 60% at 60% 90%,rgba(240,171,252,.25),transparent 60%),
|
||||
#0b1024}
|
||||
.card{backdrop-filter:blur(28px) saturate(180%);-webkit-backdrop-filter:blur(28px) saturate(180%);
|
||||
background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.18)}
|
||||
14
skills/html-ppt/assets/themes/gruvbox-dark.css
Normal file
14
skills/html-ppt/assets/themes/gruvbox-dark.css
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
/* theme: gruvbox-dark */
|
||||
:root{
|
||||
--bg:#282828;--bg-soft:#1d2021;--surface:#3c3836;--surface-2:#504945;
|
||||
--border:rgba(235,219,178,.14);--border-strong:rgba(235,219,178,.28);
|
||||
--text-1:#ebdbb2;--text-2:#d5c4a1;--text-3:#928374;
|
||||
--accent:#fabd2f;--accent-2:#fe8019;--accent-3:#b8bb26;
|
||||
--good:#b8bb26;--warn:#fabd2f;--bad:#fb4934;
|
||||
--grad:linear-gradient(135deg,#fe8019,#fabd2f 55%,#b8bb26);
|
||||
--grad-soft:linear-gradient(135deg,#3c3836,#504945);
|
||||
--radius:6px;--radius-sm:4px;--radius-lg:12px;
|
||||
--shadow:0 10px 30px rgba(0,0,0,.5);
|
||||
--shadow-lg:0 24px 60px rgba(0,0,0,.65);
|
||||
--font-sans:'Inter','Noto Sans SC',sans-serif;
|
||||
}
|
||||
21
skills/html-ppt/assets/themes/japanese-minimal.css
Normal file
21
skills/html-ppt/assets/themes/japanese-minimal.css
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
/* theme: japanese-minimal — 和风极简 */
|
||||
:root{
|
||||
--bg:#fafaf5;--bg-soft:#f2f0e6;--surface:#ffffff;--surface-2:#f5f3ea;
|
||||
--border:rgba(40,30,20,.1);--border-strong:rgba(40,30,20,.3);
|
||||
--text-1:#1a1a18;--text-2:#5c564c;--text-3:#9c958a;
|
||||
--accent:#d93a2a;--accent-2:#1a1a18;--accent-3:#c9a961;
|
||||
--good:#4a6b3e;--warn:#c9a961;--bad:#d93a2a;
|
||||
--grad:linear-gradient(135deg,#d93a2a,#1a1a18);
|
||||
--grad-soft:linear-gradient(135deg,#faeae6,#f5f3ea);
|
||||
--radius:0px;--radius-sm:0px;--radius-lg:2px;
|
||||
--shadow:none;
|
||||
--shadow-lg:0 1px 0 rgba(40,30,20,.12);
|
||||
--font-sans:'Inter','Noto Sans SC',sans-serif;
|
||||
--font-serif:'Noto Serif SC','Playfair Display',serif;
|
||||
--font-display:'Noto Serif SC','Playfair Display',serif;
|
||||
}
|
||||
h1.title,h2.title,.h1,.h2{font-weight:500;letter-spacing:.04em}
|
||||
.card{border:1px solid var(--border);box-shadow:none;padding:36px 40px}
|
||||
.divider-accent{background:var(--accent);height:2px;width:48px}
|
||||
.kicker{color:var(--accent);letter-spacing:.2em}
|
||||
.slide{padding:96px 128px}
|
||||
21
skills/html-ppt/assets/themes/magazine-bold.css
Normal file
21
skills/html-ppt/assets/themes/magazine-bold.css
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
/* theme: magazine-bold — 杂志大标题 */
|
||||
:root{
|
||||
--bg:#f5efe2;--bg-soft:#ebe4d2;--surface:#fbf6e8;--surface-2:#ede5d0;
|
||||
--border:rgba(10,10,10,.16);--border-strong:#0a0a0a;
|
||||
--text-1:#0a0a0a;--text-2:#2a2a2a;--text-3:#6a6458;
|
||||
--accent:#ea5a1a;--accent-2:#0a0a0a;--accent-3:#c42a10;
|
||||
--good:#2a6a2a;--warn:#ea5a1a;--bad:#c42a10;
|
||||
--grad:linear-gradient(135deg,#ea5a1a,#c42a10);
|
||||
--grad-soft:linear-gradient(135deg,#fbe4d0,#f5d6c0);
|
||||
--radius:0px;--radius-sm:0px;--radius-lg:2px;
|
||||
--shadow:none;
|
||||
--shadow-lg:6px 6px 0 var(--accent);
|
||||
--font-sans:'Inter','Noto Sans SC',sans-serif;
|
||||
--font-serif:'Playfair Display','Noto Serif SC',Georgia,serif;
|
||||
--font-display:'Playfair Display','Noto Serif SC',Georgia,serif;
|
||||
}
|
||||
h1.title,.h1{font-size:120px;line-height:.92;font-weight:900;letter-spacing:-.04em;font-family:var(--font-serif)}
|
||||
h2.title,.h2{font-size:72px;font-weight:800;font-family:var(--font-serif)}
|
||||
.card{border:1.5px solid var(--text-1)}
|
||||
.divider-accent{background:var(--accent);height:6px;width:90px}
|
||||
.kicker{color:var(--accent);text-transform:uppercase;font-weight:700;letter-spacing:.25em}
|
||||
20
skills/html-ppt/assets/themes/memphis-pop.css
Normal file
20
skills/html-ppt/assets/themes/memphis-pop.css
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
/* theme: memphis-pop — 孟菲斯波普 */
|
||||
:root{
|
||||
--bg:#fef6e8;--bg-soft:#fdebc7;--surface:#ffffff;--surface-2:#fff1d1;
|
||||
--border:#111111;--border-strong:#111111;
|
||||
--text-1:#111111;--text-2:#333333;--text-3:#666666;
|
||||
--accent:#ff3d8b;--accent-2:#37c2d7;--accent-3:#ffcc00;
|
||||
--good:#6ac04c;--warn:#ffcc00;--bad:#ff3d8b;
|
||||
--grad:linear-gradient(135deg,#ff3d8b,#ffcc00 50%,#37c2d7);
|
||||
--grad-soft:linear-gradient(135deg,#fdebc7,#fff1d1);
|
||||
--radius:10px;--radius-sm:6px;--radius-lg:18px;
|
||||
--shadow:5px 5px 0 #111;--shadow-lg:9px 9px 0 #111;
|
||||
--font-sans:'Space Grotesk','Inter','Noto Sans SC',sans-serif;
|
||||
--font-display:'Archivo Black',sans-serif;
|
||||
}
|
||||
.card{border:2.5px solid #111}
|
||||
body{background-image:
|
||||
radial-gradient(circle at 10% 20%,#ff3d8b 3px,transparent 4px),
|
||||
radial-gradient(circle at 80% 40%,#37c2d7 3px,transparent 4px),
|
||||
radial-gradient(circle at 30% 80%,#ffcc00 3px,transparent 4px);
|
||||
background-size:200px 200px,220px 220px,260px 260px}
|
||||
19
skills/html-ppt/assets/themes/midcentury.css
Normal file
19
skills/html-ppt/assets/themes/midcentury.css
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
/* theme: midcentury — 世纪中期现代 */
|
||||
:root{
|
||||
--bg:#f3ead8;--bg-soft:#ebdfc4;--surface:#f9f2e0;--surface-2:#e8dcbe;
|
||||
--border:rgba(60,40,20,.18);--border-strong:rgba(60,40,20,.4);
|
||||
--text-1:#201810;--text-2:#5a4830;--text-3:#9a8868;
|
||||
--accent:#d4902a;--accent-2:#2a7a7f;--accent-3:#c7502a;
|
||||
--good:#5a7a3a;--warn:#d4902a;--bad:#c7502a;
|
||||
--grad:linear-gradient(135deg,#d4902a,#c7502a 55%,#2a7a7f);
|
||||
--grad-soft:linear-gradient(135deg,#f4e0b6,#eac7a8);
|
||||
--radius:2px;--radius-sm:0px;--radius-lg:4px;
|
||||
--shadow:4px 4px 0 rgba(40,25,10,.12);
|
||||
--shadow-lg:6px 6px 0 rgba(40,25,10,.2),0 10px 24px rgba(40,25,10,.14);
|
||||
--font-sans:'Inter','Noto Sans SC',sans-serif;
|
||||
--font-display:'Playfair Display','Noto Serif SC',serif;
|
||||
}
|
||||
.card{border:1.5px solid var(--border-strong)}
|
||||
.divider-accent{background:var(--accent-3);height:4px;width:80px}
|
||||
.kicker{color:var(--accent-2)}
|
||||
h1.title,.h1{color:var(--accent-3)}
|
||||
16
skills/html-ppt/assets/themes/minimal-white.css
Normal file
16
skills/html-ppt/assets/themes/minimal-white.css
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
/* theme: minimal-white — 极简白,克制高级 */
|
||||
:root{
|
||||
--bg:#ffffff;--bg-soft:#fafafa;--surface:#ffffff;--surface-2:#f5f5f6;
|
||||
--border:rgba(17,18,22,.08);--border-strong:rgba(17,18,22,.16);
|
||||
--text-1:#0c0d10;--text-2:#55596a;--text-3:#9ca1b0;
|
||||
--accent:#111216;--accent-2:#3b3f4a;--accent-3:#6b6f7a;
|
||||
--good:#1aaf6c;--warn:#c98500;--bad:#c13a3a;
|
||||
--grad:linear-gradient(135deg,#111216,#3b3f4a);
|
||||
--grad-soft:linear-gradient(135deg,#f5f5f6,#ffffff);
|
||||
--radius:14px;--radius-sm:8px;--radius-lg:22px;
|
||||
--shadow:0 1px 2px rgba(17,18,22,.04),0 8px 24px rgba(17,18,22,.06);
|
||||
--shadow-lg:0 20px 60px rgba(17,18,22,.1);
|
||||
--font-sans:'Inter','Noto Sans SC',sans-serif;
|
||||
--font-display:'Inter','Noto Sans SC',sans-serif;
|
||||
--letter-tight:-.035em;
|
||||
}
|
||||
17
skills/html-ppt/assets/themes/neo-brutalism.css
Normal file
17
skills/html-ppt/assets/themes/neo-brutalism.css
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
/* theme: neo-brutalism — 厚描边、硬阴影、明黄 */
|
||||
:root{
|
||||
--bg:#fffef0;--bg-soft:#fffbd0;--surface:#ffffff;--surface-2:#fff38a;
|
||||
--border:#000000;--border-strong:#000000;
|
||||
--text-1:#000000;--text-2:#222222;--text-3:#555555;
|
||||
--accent:#ffd400;--accent-2:#ff5ca8;--accent-3:#3a7cff;
|
||||
--good:#00b36b;--warn:#ff9900;--bad:#ff3a30;
|
||||
--grad:linear-gradient(135deg,#ffd400,#ff5ca8);
|
||||
--grad-soft:linear-gradient(135deg,#fffbd0,#fff);
|
||||
--radius:6px;--radius-sm:4px;--radius-lg:10px;
|
||||
--shadow:6px 6px 0 #000;--shadow-lg:10px 10px 0 #000;
|
||||
--font-sans:'Space Grotesk','Inter','Noto Sans SC',sans-serif;
|
||||
--font-display:'Archivo Black','Space Grotesk',sans-serif;
|
||||
--letter-tight:-.03em;
|
||||
}
|
||||
.card{border:3px solid #000}
|
||||
.pill{border:2px solid #000;background:#ffd400;color:#000}
|
||||
20
skills/html-ppt/assets/themes/news-broadcast.css
Normal file
20
skills/html-ppt/assets/themes/news-broadcast.css
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
/* theme: news-broadcast — 新闻播报 */
|
||||
:root{
|
||||
--bg:#ffffff;--bg-soft:#f4f4f4;--surface:#ffffff;--surface-2:#ececec;
|
||||
--border:rgba(0,0,0,.14);--border-strong:#0a0a0a;
|
||||
--text-1:#0a0a0a;--text-2:#3a3a3a;--text-3:#7a7a7a;
|
||||
--accent:#e11d2d;--accent-2:#0a0a0a;--accent-3:#ffd100;
|
||||
--good:#0e7c3a;--warn:#ffd100;--bad:#e11d2d;
|
||||
--grad:linear-gradient(90deg,#e11d2d 0%,#e11d2d 100%);
|
||||
--grad-soft:linear-gradient(135deg,#fde5e7,#f4f4f4);
|
||||
--radius:0px;--radius-sm:0px;--radius-lg:2px;
|
||||
--shadow:none;
|
||||
--shadow-lg:0 4px 0 var(--accent);
|
||||
--font-sans:'Oswald','Inter','Noto Sans SC',sans-serif;
|
||||
--font-display:'Oswald','Inter','Noto Sans SC',sans-serif;
|
||||
}
|
||||
h1.title,h2.title,.h1,.h2{font-weight:700;text-transform:uppercase;letter-spacing:-.01em}
|
||||
.card{border:2px solid var(--text-1);box-shadow:6px 6px 0 var(--accent)}
|
||||
.divider-accent{background:var(--accent);height:6px;width:100%}
|
||||
.kicker{background:var(--accent);color:#fff;padding:4px 12px;display:inline-block;letter-spacing:.15em}
|
||||
.slide::before{content:"";position:absolute;left:0;top:0;bottom:0;width:8px;background:var(--accent);z-index:3}
|
||||
14
skills/html-ppt/assets/themes/nord.css
Normal file
14
skills/html-ppt/assets/themes/nord.css
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
/* theme: nord */
|
||||
:root{
|
||||
--bg:#2e3440;--bg-soft:#272b35;--surface:#3b4252;--surface-2:#434c5e;
|
||||
--border:rgba(236,239,244,.12);--border-strong:rgba(236,239,244,.24);
|
||||
--text-1:#eceff4;--text-2:#d8dee9;--text-3:#7b8394;
|
||||
--accent:#88c0d0;--accent-2:#81a1c1;--accent-3:#b48ead;
|
||||
--good:#a3be8c;--warn:#ebcb8b;--bad:#bf616a;
|
||||
--grad:linear-gradient(135deg,#88c0d0,#81a1c1 50%,#b48ead);
|
||||
--grad-soft:linear-gradient(135deg,#3b4252,#434c5e);
|
||||
--radius:12px;--radius-sm:8px;--radius-lg:20px;
|
||||
--shadow:0 10px 30px rgba(0,0,0,.35);
|
||||
--shadow-lg:0 22px 60px rgba(0,0,0,.5);
|
||||
--font-sans:'Inter','Noto Sans SC',sans-serif;
|
||||
}
|
||||
21
skills/html-ppt/assets/themes/pitch-deck-vc.css
Normal file
21
skills/html-ppt/assets/themes/pitch-deck-vc.css
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
/* theme: pitch-deck-vc — YC 风融资 pitch */
|
||||
:root{
|
||||
--bg:#ffffff;--bg-soft:#fafbfc;--surface:#ffffff;--surface-2:#f5f7fa;
|
||||
--border:rgba(20,30,50,.1);--border-strong:rgba(20,30,50,.22);
|
||||
--text-1:#0b0d12;--text-2:#4a5270;--text-3:#8b93a8;
|
||||
--accent:#0070f3;--accent-2:#7928ca;--accent-3:#ff4ecb;
|
||||
--good:#0cce6b;--warn:#f5a524;--bad:#ee0000;
|
||||
--grad:linear-gradient(135deg,#0070f3,#7928ca);
|
||||
--grad-soft:linear-gradient(135deg,#e8f0ff,#f3e8ff);
|
||||
--radius:14px;--radius-sm:8px;--radius-lg:22px;
|
||||
--shadow:0 2px 8px rgba(20,30,50,.06),0 12px 32px rgba(20,30,50,.06);
|
||||
--shadow-lg:0 8px 24px rgba(20,30,50,.1),0 30px 80px rgba(20,30,50,.1);
|
||||
--font-sans:'Inter','Noto Sans SC',sans-serif;
|
||||
--font-display:'Inter','Noto Sans SC',sans-serif;
|
||||
}
|
||||
.slide{padding:88px 120px}
|
||||
h1.title,.h1{font-weight:800;letter-spacing:-.035em}
|
||||
h1.title .gradient-text,.h1 .gradient-text{background:var(--grad);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}
|
||||
.card{border:1px solid var(--border)}
|
||||
.divider-accent{background:var(--grad);height:4px;width:64px;border-radius:2px}
|
||||
.kicker{background:var(--grad);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;color:transparent}
|
||||
16
skills/html-ppt/assets/themes/rainbow-gradient.css
Normal file
16
skills/html-ppt/assets/themes/rainbow-gradient.css
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
/* theme: rainbow-gradient — 彩虹渐变点缀(白底) */
|
||||
:root{
|
||||
--bg:#ffffff;--bg-soft:#f8f8fb;--surface:#ffffff;--surface-2:#f4f4f8;
|
||||
--border:rgba(20,20,40,.08);--border-strong:rgba(20,20,40,.2);
|
||||
--text-1:#0c0d10;--text-2:#4d5162;--text-3:#9096a8;
|
||||
--accent:#ff4d8b;--accent-2:#7a5cff;--accent-3:#36b6ff;
|
||||
--good:#1aaf6c;--warn:#f5a524;--bad:#e0445a;
|
||||
--grad:linear-gradient(90deg,#ff0080,#ff4d00,#ff9900,#ffe600,#00c853,#0091ea,#6200ea,#ff0080);
|
||||
--grad-soft:linear-gradient(135deg,#fff,#f8f8fb);
|
||||
--radius:16px;--radius-sm:10px;--radius-lg:24px;
|
||||
--shadow:0 12px 32px rgba(124,92,255,.1);
|
||||
--shadow-lg:0 24px 60px rgba(124,92,255,.18);
|
||||
--font-sans:'Inter','Noto Sans SC',sans-serif;
|
||||
}
|
||||
.gradient-text{background-size:200% auto;animation:rbflow 6s linear infinite}
|
||||
@keyframes rbflow{to{background-position:200% 0}}
|
||||
22
skills/html-ppt/assets/themes/retro-tv.css
Normal file
22
skills/html-ppt/assets/themes/retro-tv.css
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
/* theme: retro-tv — 复古显像管 */
|
||||
:root{
|
||||
--bg:#f5ecd7;--bg-soft:#efe4c6;--surface:#fbf5e2;--surface-2:#efe3c2;
|
||||
--border:rgba(120,70,20,.22);--border-strong:rgba(120,70,20,.45);
|
||||
--text-1:#2a1a08;--text-2:#6b4a22;--text-3:#a68656;
|
||||
--accent:#e67e14;--accent-2:#c73a1f;--accent-3:#f2b544;
|
||||
--good:#3e8940;--warn:#e67e14;--bad:#c73a1f;
|
||||
--grad:linear-gradient(135deg,#c73a1f,#e67e14 55%,#f2b544);
|
||||
--grad-soft:linear-gradient(135deg,#fde6c4,#fbd9a0);
|
||||
--radius:10px;--radius-sm:6px;--radius-lg:16px;
|
||||
--shadow:0 6px 0 rgba(80,40,0,.12),0 12px 28px rgba(80,40,0,.15);
|
||||
--shadow-lg:0 10px 0 rgba(80,40,0,.15),0 24px 50px rgba(80,40,0,.2);
|
||||
--font-sans:'Inter','Noto Sans SC',sans-serif;
|
||||
--font-display:'Playfair Display','Noto Serif SC',serif;
|
||||
}
|
||||
body{background:
|
||||
repeating-linear-gradient(0deg,rgba(80,40,0,.06) 0 2px,transparent 2px 4px),
|
||||
radial-gradient(ellipse at center,#f7ecd0 0%,#e8d9b0 85%,#c9b888 100%)}
|
||||
.slide::before{content:"";position:absolute;inset:0;pointer-events:none;
|
||||
background:repeating-linear-gradient(0deg,rgba(0,0,0,.035) 0 2px,transparent 2px 4px);z-index:1}
|
||||
.slide > *{position:relative;z-index:2}
|
||||
h1.title,.h1{color:var(--accent-2)}
|
||||
14
skills/html-ppt/assets/themes/rose-pine.css
Normal file
14
skills/html-ppt/assets/themes/rose-pine.css
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
/* theme: rose-pine */
|
||||
:root{
|
||||
--bg:#191724;--bg-soft:#1f1d2e;--surface:#26233a;--surface-2:#2a2740;
|
||||
--border:rgba(224,222,244,.12);--border-strong:rgba(224,222,244,.24);
|
||||
--text-1:#e0def4;--text-2:#c4b8d8;--text-3:#6e6a86;
|
||||
--accent:#ebbcba;--accent-2:#c4a7e7;--accent-3:#9ccfd8;
|
||||
--good:#31748f;--warn:#f6c177;--bad:#eb6f92;
|
||||
--grad:linear-gradient(135deg,#ebbcba,#c4a7e7 55%,#9ccfd8);
|
||||
--grad-soft:linear-gradient(135deg,#26233a,#2a2740);
|
||||
--radius:14px;--radius-sm:10px;--radius-lg:22px;
|
||||
--shadow:0 10px 30px rgba(0,0,0,.4);
|
||||
--shadow-lg:0 22px 58px rgba(0,0,0,.55);
|
||||
--font-sans:'Inter','Noto Sans SC',sans-serif;
|
||||
}
|
||||
17
skills/html-ppt/assets/themes/sharp-mono.css
Normal file
17
skills/html-ppt/assets/themes/sharp-mono.css
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
/* theme: sharp-mono — 锐利黑白高对比 */
|
||||
:root{
|
||||
--bg:#ffffff;--bg-soft:#ffffff;--surface:#ffffff;--surface-2:#000000;
|
||||
--border:#000000;--border-strong:#000000;
|
||||
--text-1:#000000;--text-2:#1a1a1a;--text-3:#4a4a4a;
|
||||
--accent:#000000;--accent-2:#000000;--accent-3:#ff2200;
|
||||
--good:#008800;--warn:#ff9900;--bad:#ff0000;
|
||||
--grad:linear-gradient(135deg,#000,#222);
|
||||
--grad-soft:linear-gradient(135deg,#fff,#eee);
|
||||
--radius:0;--radius-sm:0;--radius-lg:0;
|
||||
--shadow:4px 4px 0 #000;--shadow-lg:8px 8px 0 #000;
|
||||
--font-sans:'Archivo Black','Inter','Noto Sans SC',sans-serif;
|
||||
--font-display:'Archivo Black',sans-serif;
|
||||
--letter-tight:-.04em;
|
||||
}
|
||||
.h1,.h2,h1.title,h2.title{text-transform:uppercase}
|
||||
.card{border:2px solid #000}
|
||||
14
skills/html-ppt/assets/themes/soft-pastel.css
Normal file
14
skills/html-ppt/assets/themes/soft-pastel.css
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
/* theme: soft-pastel — 柔和马卡龙 */
|
||||
:root{
|
||||
--bg:#fdf7fb;--bg-soft:#fbeef3;--surface:#ffffff;--surface-2:#fdf0f5;
|
||||
--border:rgba(120,70,110,.12);--border-strong:rgba(120,70,110,.22);
|
||||
--text-1:#3a1f33;--text-2:#6b4d62;--text-3:#a28a99;
|
||||
--accent:#f49bb8;--accent-2:#b5d5f0;--accent-3:#f7d08a;
|
||||
--good:#9dd9a3;--warn:#f7d08a;--bad:#ef9a9a;
|
||||
--grad:linear-gradient(135deg,#f49bb8,#b5d5f0 55%,#c4a0e8);
|
||||
--grad-soft:linear-gradient(135deg,#fbeef3,#eaf4fc);
|
||||
--radius:24px;--radius-sm:16px;--radius-lg:32px;
|
||||
--shadow:0 8px 28px rgba(244,155,184,.18);
|
||||
--shadow-lg:0 24px 70px rgba(181,213,240,.3);
|
||||
--font-sans:'Inter','Noto Sans SC',sans-serif;
|
||||
}
|
||||
14
skills/html-ppt/assets/themes/solarized-light.css
Normal file
14
skills/html-ppt/assets/themes/solarized-light.css
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
/* theme: solarized-light */
|
||||
:root{
|
||||
--bg:#fdf6e3;--bg-soft:#eee8d5;--surface:#ffffff;--surface-2:#f5efd7;
|
||||
--border:rgba(88,110,117,.2);--border-strong:rgba(88,110,117,.4);
|
||||
--text-1:#073642;--text-2:#586e75;--text-3:#93a1a1;
|
||||
--accent:#268bd2;--accent-2:#2aa198;--accent-3:#d33682;
|
||||
--good:#859900;--warn:#b58900;--bad:#dc322f;
|
||||
--grad:linear-gradient(135deg,#268bd2,#2aa198 50%,#859900);
|
||||
--grad-soft:linear-gradient(135deg,#fdf6e3,#eee8d5);
|
||||
--radius:10px;--radius-sm:6px;--radius-lg:16px;
|
||||
--shadow:0 6px 20px rgba(88,110,117,.14);
|
||||
--shadow-lg:0 18px 50px rgba(88,110,117,.24);
|
||||
--font-sans:'Inter','Noto Sans SC',sans-serif;
|
||||
}
|
||||
14
skills/html-ppt/assets/themes/sunset-warm.css
Normal file
14
skills/html-ppt/assets/themes/sunset-warm.css
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
/* theme: sunset-warm — 暖色调 橘/珊瑚/琥珀 */
|
||||
:root{
|
||||
--bg:#fff7ef;--bg-soft:#ffeedc;--surface:#ffffff;--surface-2:#fff2e0;
|
||||
--border:rgba(120,60,20,.12);--border-strong:rgba(120,60,20,.22);
|
||||
--text-1:#2a160a;--text-2:#6b4630;--text-3:#a28572;
|
||||
--accent:#e36a2d;--accent-2:#f2a341;--accent-3:#d94860;
|
||||
--good:#5ea35a;--warn:#f2a341;--bad:#d94860;
|
||||
--grad:linear-gradient(135deg,#d94860,#e36a2d 50%,#f2a341);
|
||||
--grad-soft:linear-gradient(135deg,#ffeedc,#ffe0d0);
|
||||
--radius:18px;--radius-sm:12px;--radius-lg:28px;
|
||||
--shadow:0 12px 32px rgba(227,106,45,.16);
|
||||
--shadow-lg:0 24px 64px rgba(227,106,45,.22);
|
||||
--font-sans:'Inter','Noto Sans SC',sans-serif;
|
||||
}
|
||||
17
skills/html-ppt/assets/themes/swiss-grid.css
Normal file
17
skills/html-ppt/assets/themes/swiss-grid.css
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
/* theme: swiss-grid — 瑞士网格,Helvetica 感 */
|
||||
:root{
|
||||
--bg:#ffffff;--bg-soft:#f4f4f4;--surface:#ffffff;--surface-2:#f4f4f4;
|
||||
--border:#111111;--border-strong:#111111;
|
||||
--text-1:#111111;--text-2:#444444;--text-3:#888888;
|
||||
--accent:#d6001c;--accent-2:#111111;--accent-3:#888888;
|
||||
--good:#0f8a2f;--warn:#d38a00;--bad:#d6001c;
|
||||
--grad:linear-gradient(135deg,#d6001c,#111);
|
||||
--grad-soft:linear-gradient(135deg,#f4f4f4,#fff);
|
||||
--radius:0;--radius-sm:0;--radius-lg:0;
|
||||
--shadow:none;--shadow-lg:none;
|
||||
--font-sans:'Inter','Helvetica Neue',Helvetica,'Noto Sans SC',sans-serif;
|
||||
--font-display:'Inter','Helvetica Neue',Helvetica,sans-serif;
|
||||
--letter-tight:-.04em;
|
||||
}
|
||||
.card{border-top:2px solid #111;border-bottom:1px solid #111;border-left:none;border-right:none;box-shadow:none;background:#fff}
|
||||
.slide{background-image:linear-gradient(90deg,rgba(0,0,0,.04) 1px,transparent 1px);background-size:calc(100%/12) 100%}
|
||||
18
skills/html-ppt/assets/themes/terminal-green.css
Normal file
18
skills/html-ppt/assets/themes/terminal-green.css
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
/* theme: terminal-green — 绿屏终端 */
|
||||
:root{
|
||||
--bg:#030a04;--bg-soft:#041308;--surface:#0a1b10;--surface-2:#0d2614;
|
||||
--border:rgba(0,255,120,.22);--border-strong:rgba(0,255,120,.42);
|
||||
--text-1:#8cff9a;--text-2:#4bd17a;--text-3:#2f8a4d;
|
||||
--accent:#00ff88;--accent-2:#67ffd0;--accent-3:#b6ff6b;
|
||||
--good:#00ff88;--warn:#ffe066;--bad:#ff6464;
|
||||
--grad:linear-gradient(135deg,#00ff88,#67ffd0);
|
||||
--grad-soft:linear-gradient(135deg,#0a1b10,#0d2614);
|
||||
--radius:4px;--radius-sm:2px;--radius-lg:8px;
|
||||
--shadow:0 0 30px rgba(0,255,136,.15);
|
||||
--shadow-lg:0 0 60px rgba(0,255,136,.28);
|
||||
--font-sans:'JetBrains Mono','IBM Plex Mono',monospace;
|
||||
--font-display:'JetBrains Mono',monospace;
|
||||
--letter-tight:-.01em;
|
||||
}
|
||||
body{text-shadow:0 0 2px rgba(0,255,136,.5)}
|
||||
.card{border:1px solid rgba(0,255,120,.3);background:rgba(10,27,16,.6)}
|
||||
14
skills/html-ppt/assets/themes/tokyo-night.css
Normal file
14
skills/html-ppt/assets/themes/tokyo-night.css
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
/* theme: tokyo-night */
|
||||
:root{
|
||||
--bg:#1a1b26;--bg-soft:#16161e;--surface:#24283b;--surface-2:#2f334d;
|
||||
--border:rgba(192,202,245,.12);--border-strong:rgba(192,202,245,.24);
|
||||
--text-1:#c0caf5;--text-2:#a9b1d6;--text-3:#565f89;
|
||||
--accent:#7aa2f7;--accent-2:#bb9af7;--accent-3:#7dcfff;
|
||||
--good:#9ece6a;--warn:#e0af68;--bad:#f7768e;
|
||||
--grad:linear-gradient(135deg,#7aa2f7,#bb9af7 55%,#f7768e);
|
||||
--grad-soft:linear-gradient(135deg,#24283b,#2f334d);
|
||||
--radius:12px;--radius-sm:8px;--radius-lg:20px;
|
||||
--shadow:0 10px 30px rgba(0,0,0,.45);
|
||||
--shadow-lg:0 24px 62px rgba(0,0,0,.6);
|
||||
--font-sans:'Inter','Noto Sans SC',sans-serif;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue