fix: v2 — iframe theme isolation, 8 full-deck templates from source decks, 20 FX animations (particles/graph/fireworks), +12 themes
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
|
@ -0,0 +1,140 @@
|
||||||
|
# Fix Brief · html-ppt v2
|
||||||
|
|
||||||
|
User feedback: current skill is weak. Fix these four problems HARD. Do not hand-wave.
|
||||||
|
|
||||||
|
Skill lives at `~/clawspace/html-ppt-skill`. Git is already initialized, SKILL.md + assets already exist. This is an **iteration**, keep what works, replace what's weak.
|
||||||
|
|
||||||
|
## Problem 1 — Theme showcase shows identical pages
|
||||||
|
|
||||||
|
Root cause: `templates/theme-showcase.html` builds 24 slides but they all inherit one `<link id="theme-link">`. When you arrow-through, every slide looks the same.
|
||||||
|
|
||||||
|
Fix: Each slide in theme-showcase must render **its own theme in isolation**. Do this with an **`<iframe srcdoc>` per slide**, where the iframe loads `assets/base.css` + the specific theme CSS + a small demo content block. That way slide N = theme N, guaranteed visually different. Rebuild the showcase so pressing → actually shows a new look each time. Do the same for any other "all themes in one deck" pattern.
|
||||||
|
|
||||||
|
Verify with headless chrome: render slide 1, slide 5, slide 12, slide 20 to PNG and confirm they look different.
|
||||||
|
|
||||||
|
## Problem 2 — Did not absorb user's existing deck styles
|
||||||
|
|
||||||
|
**REQUIRED**: deeply survey these decks, extract 6–10 distinct "full deck looks" and turn each into a named **full-deck template** under `templates/full-decks/<name>/index.html` with scoped CSS + demo content:
|
||||||
|
|
||||||
|
Sources (read-only, DO NOT copy content, extract style):
|
||||||
|
|
||||||
|
- `/Volumes/luluDrive/作品素材/视频/20260405-Karpathy-知识库/20260405 演示幻灯片【方向键版】.html`
|
||||||
|
- `/Volumes/luluDrive/作品素材/视频/20260405-Karpathy-知识库/20260405 架构图v2.html`
|
||||||
|
- `/Volumes/luluDrive/作品素材/视频/20260406-obsidian-claude/slides.html`
|
||||||
|
- `/Volumes/luluDrive/作品素材/视频/20260409-升级版知识库/presentation.html`
|
||||||
|
- `/Volumes/luluDrive/作品素材/视频/20260409-升级版知识库/小红书图文/v2-白底版/html源文件/slide_01_cover.html` (+ others in same dir)
|
||||||
|
- `/Volumes/luluDrive/作品素材/视频/20260412-AI测试与安全/html/xhs-ai-testing-safety-v2.html`
|
||||||
|
- `/Volumes/luluDrive/作品素材/视频/20260412-obsidian-skills/html/xhs-obsidian-skills.html`
|
||||||
|
- `/Volumes/luluDrive/作品素材/视频/20260413-graphify/ppt/graphify.html`
|
||||||
|
- `/Volumes/luluDrive/作品素材/视频/20260413-graphify/小红书图文/*.html`
|
||||||
|
- `/Volumes/luluDrive/作品素材/视频/20260414-hermes-agent/ppt/hermes-vs-openclaw.html`
|
||||||
|
- `/Volumes/luluDrive/作品素材/视频/20260414-hermes-agent/ppt/hermes-record.html`
|
||||||
|
- `/Volumes/luluDrive/作品素材/视频/20260414-hermes-agent/小红书图文/*.html`
|
||||||
|
|
||||||
|
For each, extract: background treatment, gradient usage, font pairing, card style, accent color, hero typography scale, footer pattern. Turn the strongest 8+ into **full-deck templates**, each a complete multi-slide deck with cover / section / content / code / chart / CTA / thanks. Give each a distinctive name and register it in `references/full-decks.md`. Suggested names (rename as you see fit after surveying):
|
||||||
|
|
||||||
|
- `xhs-white-editorial` — 小红书白底杂志风 (from 20260409 v2-白底版 + 20260412 xhs)
|
||||||
|
- `graphify-dark-graph` — 暗底 + 力导向知识图谱 (from 20260413 graphify)
|
||||||
|
- `knowledge-arch-blueprint` — 蓝图/架构图风 (from Karpathy 架构图v2)
|
||||||
|
- `hermes-cyber-terminal` — 终端/cyber (from hermes-record + hermes-vs-openclaw)
|
||||||
|
- `obsidian-claude-gradient` — 紫色渐变卡 (from 20260406)
|
||||||
|
- `testing-safety-alert` — 红/琥珀警示风 (from 20260412-AI测试与安全)
|
||||||
|
- `xhs-pastel-card` — 柔和马卡龙图文 (from several xhs decks)
|
||||||
|
- `dir-key-nav-minimal` — 方向键极简 (from Karpathy 方向键版)
|
||||||
|
|
||||||
|
Each full-deck template must be a SELF-CONTAINED folder under `templates/full-decks/<name>/` with `index.html`, `style.css` (scoped with `.tpl-<name>` prefix so two templates don't collide), optional `script.js`, and a README snippet describing the look.
|
||||||
|
|
||||||
|
## Problem 3 — Animations are too thin, all single-element
|
||||||
|
|
||||||
|
Current animations are mostly one-element CSS transitions. User wants **multi-element, particle, explosion, knowledge-graph** energy. Add a new layer:
|
||||||
|
|
||||||
|
Create `assets/animations/fx/` with each effect as a self-contained JS module (each has a `init(el, opts)` function). Each effect must work by adding `data-fx="<name>"` to a container. Runtime auto-inits on slide enter.
|
||||||
|
|
||||||
|
Mandatory effect set (all must be implemented, not stubs):
|
||||||
|
|
||||||
|
- **particle-burst** — canvas particles exploding from a point
|
||||||
|
- **confetti-cannon** — multi-directional confetti (colored rects + rotation)
|
||||||
|
- **firework** — rocket + explosion particles, loops
|
||||||
|
- **starfield** — scrolling starfield background
|
||||||
|
- **matrix-rain** — Matrix-style falling chars
|
||||||
|
- **knowledge-graph** — canvas force-directed graph (20-40 nodes, labeled edges, animated physics)
|
||||||
|
- **neural-net** — feedforward network with pulse signals traveling along edges
|
||||||
|
- **constellation** — points connected by lines when close (classic particles.js vibe)
|
||||||
|
- **orbit-ring** — concentric orbital dots, each at different speed
|
||||||
|
- **galaxy-swirl** — spiral particle galaxy
|
||||||
|
- **word-cascade** — words from a list rain down + pile up
|
||||||
|
- **letter-explode** — heading letters fly in from random directions
|
||||||
|
- **chain-react** — row of cards trigger each other domino-style
|
||||||
|
- **magnetic-field** — particles following an invisible magnetic curve
|
||||||
|
- **data-stream** — rows of scrolling numeric/hex data (cyber feel)
|
||||||
|
- **gradient-blob** — big blurred blobs morphing with SVG feTurbulence
|
||||||
|
- **sparkle-trail** — cursor trail of sparkles (slide-scoped)
|
||||||
|
- **shockwave** — expanding ring on slide enter
|
||||||
|
- **typewriter-multi** — multi-line typewriter with blinking cursors
|
||||||
|
- **counter-explosion** — stat counter that then bursts particles when it finishes
|
||||||
|
|
||||||
|
Plus keep the existing CSS anim set. Document each in `references/animations.md` with name + use case + how to enable (`data-fx="..."`).
|
||||||
|
|
||||||
|
Create `templates/animation-showcase.html` v2: one slide per effect (CSS + FX), each clearly labeled, auto-plays on enter, with a replay button.
|
||||||
|
|
||||||
|
## Problem 4 — Not enough themes / templates overall
|
||||||
|
|
||||||
|
Add these additional themes (each a CSS token file + verifiable distinct look):
|
||||||
|
|
||||||
|
- `cyberpunk-neon` — 黑底 + 霓虹粉/青/黄
|
||||||
|
- `y2k-chrome` — Y2K 镜面 + 渐变
|
||||||
|
- `retro-tv` — CRT 扫描线 + 暖黄
|
||||||
|
- `japanese-minimal` — 和风留白 + 朱红
|
||||||
|
- `vaporwave` — 蒸汽波粉紫
|
||||||
|
- `midcentury` — 中世纪现代 (mustard/teal/cream)
|
||||||
|
- `corporate-clean` — 企业 PPT 商务
|
||||||
|
- `academic-paper` — 学术白皮书 (Computer Modern)
|
||||||
|
- `news-broadcast` — 红白新闻图
|
||||||
|
- `pitch-deck-vc` — YC 风格投资路演
|
||||||
|
- `magazine-bold` — 杂志大字封面
|
||||||
|
- `engineering-whiteprint` — 白底蓝线工程图
|
||||||
|
|
||||||
|
Plus: add **full-deck templates** (beyond the 8 extracted above) for common scenarios:
|
||||||
|
|
||||||
|
- `pitch-deck` (problem → solution → market → traction → team → ask)
|
||||||
|
- `product-launch`
|
||||||
|
- `tech-sharing` (技术分享)
|
||||||
|
- `weekly-report`
|
||||||
|
- `xhs-post` (9-slide 小红书图文, 3:4)
|
||||||
|
- `course-module` (教学模块)
|
||||||
|
|
||||||
|
Each in `templates/full-decks/<name>/` with scoped CSS.
|
||||||
|
|
||||||
|
## Integration
|
||||||
|
|
||||||
|
- Update `SKILL.md` to list every theme, full-deck template, and fx animation with one-liners and trigger intent. Keep SKILL.md tight (<250 lines); long lists go in `references/`.
|
||||||
|
- Update `references/themes.md`, `references/animations.md`, and add `references/full-decks.md`.
|
||||||
|
- Update `templates/theme-showcase.html`, `templates/animation-showcase.html`, `templates/layout-showcase.html` to actually demonstrate the richer set. Theme showcase MUST use iframe isolation.
|
||||||
|
- Add `templates/full-decks-index.html` — a gallery deck where each slide is a live thumbnail (iframe) of a full-deck template so user can browse them.
|
||||||
|
- Verify with headless Chrome: render 6 representative slides from theme-showcase, 6 from animation-showcase (as PNGs under `scripts/verify-output/`) and confirm they look visibly different. Include this check in the final commit.
|
||||||
|
|
||||||
|
## Process
|
||||||
|
|
||||||
|
1. FIRST read the existing skill and figure out what to keep vs replace.
|
||||||
|
2. Survey the source decks (read only a handful of bytes per file — use head/grep). Do not try to load all of them at once.
|
||||||
|
3. Implement Problem 1 fix (iframe isolation) first and verify with headless chrome before moving on.
|
||||||
|
4. Then Problem 3 (FX animations) — each as its own file under `assets/animations/fx/<name>.js`. Write a tiny `fx-runtime.js` that scans `[data-fx]` on slide enter and calls the right `init`.
|
||||||
|
5. Then Problem 2 (full-deck templates extracted from videos) and Problem 4 (more themes + scenario templates).
|
||||||
|
6. Update docs + showcases.
|
||||||
|
7. Verify: run `scripts/render.sh` against the three showcases, dump PNGs, eyeball them (save to `scripts/verify-output/`).
|
||||||
|
8. Commit as author `lewis <sudolewis@gmail.com>` with message `fix: v2 — iframe theme isolation, 8 full-deck templates from source decks, 20 FX animations (particles/graph/fireworks), +12 themes`.
|
||||||
|
9. DO NOT push — orchestrator will push.
|
||||||
|
|
||||||
|
When COMPLETELY finished, run:
|
||||||
|
|
||||||
|
```
|
||||||
|
openclaw system event --text "Done: html-ppt v2 — $(ls templates/full-decks 2>/dev/null | wc -l | tr -d ' ') full decks, $(ls assets/themes | wc -l | tr -d ' ') themes, $(ls assets/animations/fx 2>/dev/null | wc -l | tr -d ' ') FX" --mode now
|
||||||
|
```
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
|
||||||
|
- Be a senior designer. If something looks boring, fix it.
|
||||||
|
- Scoped CSS per full-deck template (`.tpl-<name> …`) so loading many in one page doesn't conflict.
|
||||||
|
- Pure static, CDN deps ok (chart.js, highlight.js, canvas-confetti optional but prefer hand-rolled canvas for the FX so they're learnable).
|
||||||
|
- Chinese + English both render cleanly (Noto Sans SC / Noto Serif SC imported in `assets/fonts.css`).
|
||||||
|
- Make the theme-showcase actually differ slide by slide. This is the one test I will run first.
|
||||||
33
SKILL.md
|
|
@ -10,11 +10,14 @@ 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`.
|
All pages share a token-based design system in `assets/base.css`.
|
||||||
|
|
||||||
Tell the user this skill gives them:
|
Tell the user this skill gives them:
|
||||||
- **24 themes** (`assets/themes/*.css`) from minimal-white to aurora
|
- **36 themes** (`assets/themes/*.css`) — 24 v1 + 12 v2 (cyberpunk-neon, y2k-chrome, retro-tv, japanese-minimal, vaporwave, midcentury, corporate-clean, academic-paper, news-broadcast, pitch-deck-vc, magazine-bold, engineering-whiteprint)
|
||||||
|
- **14 full-deck templates** (`templates/full-decks/<name>/`) — complete multi-slide decks with scoped `.tpl-<name>` CSS. 8 extracted from real-world decks, 6 generic scenario scaffolds (pitch-deck, product-launch, tech-sharing, weekly-report, xhs-post 3:4, course-module).
|
||||||
- **30 layouts** (`templates/single-page/*.html`) with realistic demo data
|
- **30 layouts** (`templates/single-page/*.html`) with realistic demo data
|
||||||
- **25 named animations** (`assets/animations/animations.css`) applied via `data-anim`
|
- **25 named 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/S/O
|
- **Keyboard runtime** (`assets/runtime.js`) — arrows, T (theme), A (anim), F/S/O
|
||||||
- **Showcase decks** for theme / layout / animation catalogs
|
- **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
|
- **Headless Chrome render script** for PNG export
|
||||||
|
|
||||||
## When to use
|
## When to use
|
||||||
|
|
@ -39,7 +42,13 @@ text/notes into a presentable deck. Prefer this over building from scratch.
|
||||||
Catalog in [references/layouts.md](references/layouts.md).
|
Catalog in [references/layouts.md](references/layouts.md).
|
||||||
4. **Add animations.** Put `data-anim="fade-up"` (or `class="anim-fade-up"`) on
|
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.
|
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).
|
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`.
|
||||||
5. **Render to PNG.**
|
5. **Render to PNG.**
|
||||||
```bash
|
```bash
|
||||||
./scripts/render.sh templates/theme-showcase.html # one shot
|
./scripts/render.sh templates/theme-showcase.html # one shot
|
||||||
|
|
@ -73,9 +82,10 @@ Chinese + English deck, and how to export.
|
||||||
|
|
||||||
## Catalogs (load when needed)
|
## Catalogs (load when needed)
|
||||||
|
|
||||||
- [references/themes.md](references/themes.md) — all 24 themes with when-to-use.
|
- [references/themes.md](references/themes.md) — all 36 themes with when-to-use.
|
||||||
- [references/layouts.md](references/layouts.md) — all 30 layout types.
|
- [references/layouts.md](references/layouts.md) — all 30 layout types.
|
||||||
- [references/animations.md](references/animations.md) — all 25 animations.
|
- [references/animations.md](references/animations.md) — 25 CSS + 20 canvas FX animations.
|
||||||
|
- [references/full-decks.md](references/full-decks.md) — all 14 full-deck templates.
|
||||||
- [references/authoring-guide.md](references/authoring-guide.md) — full workflow.
|
- [references/authoring-guide.md](references/authoring-guide.md) — full workflow.
|
||||||
|
|
||||||
## File structure
|
## File structure
|
||||||
|
|
@ -88,13 +98,18 @@ html-ppt/
|
||||||
│ ├── base.css (tokens + primitives — do not edit per deck)
|
│ ├── base.css (tokens + primitives — do not edit per deck)
|
||||||
│ ├── fonts.css (webfont imports)
|
│ ├── fonts.css (webfont imports)
|
||||||
│ ├── runtime.js (keyboard + presenter + overview + theme cycle)
|
│ ├── runtime.js (keyboard + presenter + overview + theme cycle)
|
||||||
│ ├── themes/*.css (24 token overrides, one per theme)
|
│ ├── themes/*.css (36 token overrides, one per theme)
|
||||||
│ └── animations/animations.css (25 named entry animations)
|
│ └── animations/
|
||||||
|
│ ├── animations.css (25 named CSS entry animations)
|
||||||
|
│ ├── fx-runtime.js (auto-init [data-fx] on slide enter)
|
||||||
|
│ └── fx/*.js (20 canvas FX modules: particles/graph/fireworks…)
|
||||||
├── templates/
|
├── templates/
|
||||||
│ ├── deck.html (minimal 6-slide starter)
|
│ ├── deck.html (minimal 6-slide starter)
|
||||||
│ ├── theme-showcase.html (24 slides, one per theme)
|
│ ├── theme-showcase.html (36 slides, iframe-isolated per theme)
|
||||||
│ ├── layout-showcase.html (iframe tour of all 30 layouts)
|
│ ├── layout-showcase.html (iframe tour of all 30 layouts)
|
||||||
│ ├── animation-showcase.html (27 slides, one per animation)
|
│ ├── 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 (30 layout files with demo data)
|
│ └── single-page/*.html (30 layout files with demo data)
|
||||||
├── scripts/
|
├── scripts/
|
||||||
│ ├── new-deck.sh (scaffold a deck from deck.html)
|
│ ├── new-deck.sh (scaffold a deck from deck.html)
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
@ -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);
|
||||||
|
})();
|
||||||
|
|
@ -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(); } };
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
@ -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(); } };
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
@ -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(); } };
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
@ -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); } };
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
@ -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(); } };
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
@ -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(); } };
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
@ -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(); } };
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
@ -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(); } };
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
@ -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(); } };
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
@ -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); } };
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
@ -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(); } };
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
@ -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(); } };
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
@ -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(); } };
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
@ -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(); } };
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
@ -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(); } };
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
@ -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(); } };
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
@ -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();
|
||||||
|
}};
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
@ -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(); } };
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
@ -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); } };
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
@ -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(); } };
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
@ -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}
|
||||||
|
|
@ -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)}
|
||||||
|
|
@ -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)}
|
||||||
|
|
@ -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}
|
||||||
|
|
@ -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}
|
||||||
|
|
@ -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}
|
||||||
|
|
@ -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)}
|
||||||
|
|
@ -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}
|
||||||
|
|
@ -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}
|
||||||
|
|
@ -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)}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
/* theme: vaporwave — 蒸汽波 */
|
||||||
|
:root{
|
||||||
|
--bg:#1a0938;--bg-soft:#261050;--surface:rgba(255,255,255,.06);--surface-2:rgba(255,255,255,.1);
|
||||||
|
--border:rgba(255,110,199,.28);--border-strong:rgba(0,245,255,.5);
|
||||||
|
--text-1:#fdf0ff;--text-2:#d4a8e8;--text-3:#8a6ba8;
|
||||||
|
--accent:#ff6ec7;--accent-2:#00f5ff;--accent-3:#ffd166;
|
||||||
|
--grad:linear-gradient(135deg,#ff6ec7 0%,#c94fff 35%,#00f5ff 100%);
|
||||||
|
--grad-soft:linear-gradient(135deg,rgba(255,110,199,.25),rgba(0,245,255,.25));
|
||||||
|
--radius:18px;--radius-sm:10px;--radius-lg:28px;
|
||||||
|
--shadow:0 20px 60px rgba(255,110,199,.2),0 0 1px rgba(0,245,255,.6);
|
||||||
|
--shadow-lg:0 30px 80px rgba(255,110,199,.3),0 0 2px rgba(0,245,255,.8);
|
||||||
|
--font-sans:'Space Grotesk','Inter','Noto Sans SC',sans-serif;
|
||||||
|
--font-display:'Space Grotesk','Inter',sans-serif;
|
||||||
|
}
|
||||||
|
body{background:
|
||||||
|
linear-gradient(180deg,#1a0938 0%,#3a0f5c 45%,#7a1f6b 85%,#e85d9c 100%),
|
||||||
|
radial-gradient(ellipse at 50% 80%,rgba(0,245,255,.3),transparent 60%)}
|
||||||
|
h1.title,.h1{background:var(--grad);-webkit-background-clip:text;background-clip:text;
|
||||||
|
-webkit-text-fill-color:transparent;color:transparent}
|
||||||
|
.card{backdrop-filter:blur(18px)}
|
||||||
|
.divider-accent{background:var(--grad);height:4px;width:120px;box-shadow:0 0 20px var(--accent)}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
/* theme: y2k-chrome — 千禧银色铬金属 */
|
||||||
|
:root{
|
||||||
|
--bg:#dfe4ec;--bg-soft:#eef1f6;--surface:rgba(255,255,255,.72);--surface-2:rgba(255,255,255,.5);
|
||||||
|
--border:rgba(120,135,170,.32);--border-strong:rgba(80,100,140,.55);
|
||||||
|
--text-1:#1a1f2e;--text-2:#4a536a;--text-3:#8590a6;
|
||||||
|
--accent:#8a5cff;--accent-2:#3ccfd8;--accent-3:#ff84c4;
|
||||||
|
--grad:linear-gradient(135deg,#b8c4d8 0%,#f5f7fb 30%,#8a9ab8 55%,#e8ecf4 80%,#6b7a95 100%);
|
||||||
|
--grad-soft:linear-gradient(135deg,#c9e4ff,#f5d6ff 50%,#d6fffa);
|
||||||
|
--radius:26px;--radius-sm:16px;--radius-lg:36px;
|
||||||
|
--shadow:0 12px 30px rgba(70,90,130,.22),inset 0 1px 0 rgba(255,255,255,.9),inset 0 -1px 0 rgba(80,100,140,.2);
|
||||||
|
--shadow-lg:0 24px 60px rgba(70,90,130,.35),inset 0 2px 0 rgba(255,255,255,.95);
|
||||||
|
--font-sans:'Space Grotesk','Inter','Noto Sans SC',sans-serif;
|
||||||
|
--font-display:'Space Grotesk','Inter',sans-serif;
|
||||||
|
}
|
||||||
|
body{background:
|
||||||
|
linear-gradient(135deg,#c4cfe0 0%,#f0f3f8 25%,#aab8d0 50%,#f5f7fb 75%,#b8c4d8 100%)}
|
||||||
|
h1.title,.h1{background:linear-gradient(180deg,#f8faff 0%,#9aa8c4 50%,#4a5670 100%);
|
||||||
|
-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;color:transparent}
|
||||||
|
.card{backdrop-filter:blur(16px) saturate(140%);-webkit-backdrop-filter:blur(16px) saturate(140%)}
|
||||||
|
.pill{background:linear-gradient(180deg,#fff,#d4dcec);border-color:rgba(120,135,170,.4)}
|
||||||
|
|
@ -91,3 +91,57 @@ All animations are disabled automatically when
|
||||||
messy.
|
messy.
|
||||||
- Stagger lists + a single hero entry = clean rhythm.
|
- Stagger lists + a single hero entry = clean rhythm.
|
||||||
- For counter-up, pair with `stat-highlight.html` or `kpi-grid.html`.
|
- For counter-up, pair with `stat-highlight.html` or `kpi-grid.html`.
|
||||||
|
|
||||||
|
## FX (canvas)
|
||||||
|
|
||||||
|
CSS animations are fire-and-forget entry effects. **FX** are live, continuously
|
||||||
|
running canvas/DOM effects that start when their slide becomes active and stop
|
||||||
|
when it leaves. They are loaded by `assets/animations/fx-runtime.js`, which
|
||||||
|
dynamically pulls every module under `assets/animations/fx/*.js` and watches
|
||||||
|
`.slide.is-active` to run lifecycle.
|
||||||
|
|
||||||
|
Add to any page:
|
||||||
|
```html
|
||||||
|
<script src="../assets/animations/fx-runtime.js"></script>
|
||||||
|
```
|
||||||
|
|
||||||
|
Then drop one of these into any slide:
|
||||||
|
```html
|
||||||
|
<div data-fx="particle-burst" style="width:100%;height:360px;"></div>
|
||||||
|
```
|
||||||
|
|
||||||
|
The container just needs a size — the FX auto-sizes a canvas to fit with
|
||||||
|
`ResizeObserver` + DPR correction. Colors read your theme (`--accent`,
|
||||||
|
`--accent-2`, `--ok`, `--warn`, `--danger`).
|
||||||
|
|
||||||
|
| name | effect | use case | trigger |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `particle-burst` | Particles explode from center, gravity + fade, re-bursts every 2.5s. | Reveal moments, stat pages. | `<div data-fx="particle-burst">` |
|
||||||
|
| `confetti-cannon` | Colored rotating rects arcing from both bottom corners. | Thank you / success pages. | `<div data-fx="confetti-cannon">` |
|
||||||
|
| `firework` | Rockets from bottom explode into colored sparks, continuous. | Celebration, launch slides. | `<div data-fx="firework">` |
|
||||||
|
| `starfield` | 3D perspective starfield flying outward. | Sci-fi / deep space backgrounds. | `<div data-fx="starfield">` |
|
||||||
|
| `matrix-rain` | Falling green katakana + hex columns. | Cyber / security / data theme. | `<div data-fx="matrix-rain">` |
|
||||||
|
| `knowledge-graph` | Force-directed graph, 28 labeled nodes, ~50 edges, live physics. | Knowledge / RAG / graph slides. | `<div data-fx="knowledge-graph">` |
|
||||||
|
| `neural-net` | 4-6-6-3 feedforward net with pulses traveling along edges. | ML / model architecture slides. | `<div data-fx="neural-net">` |
|
||||||
|
| `constellation` | Drifting points, linked when within 150 px, opacity by distance. | Ambient hero backgrounds. | `<div data-fx="constellation">` |
|
||||||
|
| `orbit-ring` | 5 concentric rings with dots at different speeds, radial glow. | System / planet / layered concepts. | `<div data-fx="orbit-ring">` |
|
||||||
|
| `galaxy-swirl` | Logarithmic spiral of ~800 particles, slow rotation. | Cover pages, intros. | `<div data-fx="galaxy-swirl">` |
|
||||||
|
| `word-cascade` | Words fall from top, pile up at bottom. | Vocabulary / concept cloud slides. | `<div data-fx="word-cascade">` |
|
||||||
|
| `letter-explode` | Heading letters fly in from random directions, loops every ~4.5s. | Big titles, hero text. | `<div data-fx="letter-explode" data-fx-text-value="EXPLODE">` |
|
||||||
|
| `chain-react` | 8 circles with a domino pulse wave traveling across. | Pipeline / sequential flow. | `<div data-fx="chain-react">` |
|
||||||
|
| `magnetic-field` | Particles travel bezier/sin curves leaving trails. | Energy / flow / abstract. | `<div data-fx="magnetic-field">` |
|
||||||
|
| `data-stream` | Rows of scrolling hex/binary text, cyberpunk. | Data, API, security. | `<div data-fx="data-stream">` |
|
||||||
|
| `gradient-blob` | 4 drifting blurred radial gradients (additive). | Soft hero backgrounds. | `<div data-fx="gradient-blob">` |
|
||||||
|
| `sparkle-trail` | Pointer-driven sparkle emitter (auto-wiggles if idle). | Interactive reveal, hover canvases. | `<div data-fx="sparkle-trail">` |
|
||||||
|
| `shockwave` | Expanding rings from center on loop. | Impact, launch, alert. | `<div data-fx="shockwave">` |
|
||||||
|
| `typewriter-multi` | 3 lines typing concurrently with blinking block cursors (DOM). | Terminal, agent boot log. | `<div data-fx="typewriter-multi" data-fx-line1="> boot...">` |
|
||||||
|
| `counter-explosion` | Number counts 0 → target, bursts particles, resets after 4s. | KPI reveal, record highs. | `<div data-fx="counter-explosion" data-fx-to="2400">` |
|
||||||
|
|
||||||
|
FX tips:
|
||||||
|
- One FX per slide is almost always enough. Mix with regular CSS `data-anim`
|
||||||
|
effects for layered polish.
|
||||||
|
- The container needs an explicit size (height) — the canvas fills 100%.
|
||||||
|
- Every module respects theme custom properties. Set `--accent` / `--accent-2`
|
||||||
|
on the slide or element to recolor on the fly.
|
||||||
|
- Lifecycle is automatic: entering a slide starts the FX, leaving stops it and
|
||||||
|
frees the canvas. You can also call `window.__hpxReinit(el)` manually.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,95 @@
|
||||||
|
# Full-Deck Templates
|
||||||
|
|
||||||
|
Self-contained multi-slide HTML decks under `templates/full-decks/<name>/`. Each folder contains:
|
||||||
|
|
||||||
|
- `index.html` — complete multi-slide deck (cover / section / content / code / chart or diagram / CTA / thanks, 7+ slides)
|
||||||
|
- `style.css` — scoped with `.tpl-<name>` class prefix so multiple templates can coexist
|
||||||
|
- `README.md` — short rationale, inspiration, and use guidance
|
||||||
|
|
||||||
|
All templates pull the shared `assets/fonts.css`, `assets/base.css`, and `assets/runtime.js` from the skill root. Navigate with `← →` / `space`, use `F` for fullscreen, `O` for overview.
|
||||||
|
|
||||||
|
Use these when you want a coherent, opinionated look for an entire deck — not a mix-and-match of layouts. Each template is visually distinctive enough to be identified at a glance.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. xhs-white-editorial — 白底杂志风
|
||||||
|
|
||||||
|
- **Source inspiration:** `20260409 升级版知识库/小红书图文/v2-白底版/slide_01_cover.html` + `20260412-AI测试与安全/html/xhs-ai-testing-safety-v2.html`
|
||||||
|
- **Key visual traits:** pure-white background, top 10-color rainbow bar, 80-110px display headlines, purple→blue→green→orange→pink gradient text, macaron soft-card set (soft-purple/pink/blue/green/orange), black-on-white `.focus` pills, hero quote box.
|
||||||
|
- **When to use:** dual-purpose XHS image + horizontal deck; dense text with strong emphasis; Chinese-first audience.
|
||||||
|
- **Path:** `templates/full-decks/xhs-white-editorial/index.html`
|
||||||
|
|
||||||
|
## 2. graphify-dark-graph — 暗底知识图谱
|
||||||
|
|
||||||
|
- **Source inspiration:** `20260413-graphify/ppt/graphify.html`
|
||||||
|
- **Key visual traits:** `#06060c→#0e1020` deep-night gradient, drifting blur orbs, SVG force-directed graph overlay on cover, rainbow-shift gradient headlines, JetBrains Mono command-line glow, glass-morphism cards (warm/blue/green/purple/danger). Accent palette: amber `#e8a87c`, mint `#7ed3a4`, mist-blue `#7eb8da`, lilac `#b8a4d6`.
|
||||||
|
- **When to use:** dev-tool / CLI / knowledge-graph / data-viz launches; live-demo decks that want an "AI-native + sci-fi + warm" vibe.
|
||||||
|
- **Path:** `templates/full-decks/graphify-dark-graph/index.html`
|
||||||
|
|
||||||
|
## 3. knowledge-arch-blueprint — 奶油蓝图架构
|
||||||
|
|
||||||
|
- **Source inspiration:** `20260405-Karpathy-知识库/20260405 架构图v2.html`
|
||||||
|
- **Key visual traits:** cream paper `#F0EAE0` base, single rust accent `#B5392A`, 48px blueprint grid mask, hard 2px black border cards, pipeline step-boxes with one hero raised, right-side rust insight callout, Playfair serif big numbers, SVG dashed feedback-loop arrows. Zero gradients, zero soft shadows.
|
||||||
|
- **When to use:** system architecture diagrams, data-flow maps, engineering white-papers; you want a serious, printable, README-friendly feel.
|
||||||
|
- **Path:** `templates/full-decks/knowledge-arch-blueprint/index.html`
|
||||||
|
|
||||||
|
## 4. hermes-cyber-terminal — 暗终端 honest-review
|
||||||
|
|
||||||
|
- **Source inspiration:** `20260414-hermes-agent/ppt/hermes-record.html` + `hermes-vs-openclaw.html`
|
||||||
|
- **Key visual traits:** `#0a0c10` black, 56px cyber grid + CRT vignette + scanlines, window traffic-light chrome, `$ prompt` command-line headlines, mint-green `#7ed3a4` glow big text, JetBrains Mono throughout, stroke-only bar charts, blinking cursor, amber/green/red tag hierarchy, dark code box.
|
||||||
|
- **When to use:** reviews of CLI / agent / dev tools with trace, diff, and benchmarks; when you want the "honest technical reviewer" voice.
|
||||||
|
- **Path:** `templates/full-decks/hermes-cyber-terminal/index.html`
|
||||||
|
|
||||||
|
## 5. obsidian-claude-gradient — GitHub 暗紫渐变
|
||||||
|
|
||||||
|
- **Source inspiration:** `20260406-obsidian-claude/slides.html`
|
||||||
|
- **Key visual traits:** GitHub-dark `#0d1117`, purple+blue radial ambient plus 60px masked grid, center-aligned layout, purple pill tags, three-stop gradient text `#a855f7→#60a5fa→#34d399`, GitHub-ish code palette (`#010409` bg + purple/blue/orange/green tokens), purple-left-border highlight block.
|
||||||
|
- **When to use:** developer workflow / MCP / Agent / dev-tool tutorials; feels like GitHub Blog / Linear Changelog; config + steps heavy content.
|
||||||
|
- **Path:** `templates/full-decks/obsidian-claude-gradient/index.html`
|
||||||
|
|
||||||
|
## 6. testing-safety-alert — 红琥珀警示
|
||||||
|
|
||||||
|
- **Source inspiration:** `20260412-AI测试与安全/html/xhs-ai-testing-safety-v2.html`
|
||||||
|
- **Key visual traits:** top and bottom 45° red-black hazard stripes, red strike-through negation headlines, L1/L2/L3 green/amber/red tier cards, alert-box with circular status dot, policy-yaml code block with red left border and `bad` keyword highlighting, red/green checklist, Q1 incident stacked bar chart.
|
||||||
|
- **When to use:** safety / risk / incident post-mortem / red-team / pre-launch AI review / policy-as-code; when the audience needs to feel "this is serious, don't skim".
|
||||||
|
- **Path:** `templates/full-decks/testing-safety-alert/index.html`
|
||||||
|
|
||||||
|
## 7. xhs-pastel-card — 柔和马卡龙慢生活
|
||||||
|
|
||||||
|
- **Source inspiration:** `20260412-obsidian-skills/html/xhs-obsidian-skills.html` + pastel patterns shared with `20260409` v2-白底版
|
||||||
|
- **Key visual traits:** cream `#fef8f1` base, three soft blurred blobs, Playfair italic serif display headlines mixed with sans body, full-color 28px rounded macaron cards (peach / mint / sky / lilac / lemon / rose), italic Playfair `01-04` numerals, SVG donut chart, chip+page topbar.
|
||||||
|
- **When to use:** lifestyle / personal-growth / slow-living / emotional content; when you want a "magazine, handmade, not-so-techy" feel; themes like rest, pause, softness.
|
||||||
|
- **Path:** `templates/full-decks/xhs-pastel-card/index.html`
|
||||||
|
|
||||||
|
## 8. dir-key-nav-minimal — 方向键 8 色极简
|
||||||
|
|
||||||
|
- **Source inspiration:** `20260405-Karpathy-知识库/20260405 演示幻灯片【方向键版】.html`
|
||||||
|
- **Key visual traits:** 8 slides each on its own mono background (indigo / cream / crimson / emerald / slate / violet / white / charcoal), each with its own accent color, 160px display headline + 4px stubby accent line divider, arrow `→` prefixed Mono list, bottom-left `← →` kbd hint plus bottom-right page label, huge breathing negative space.
|
||||||
|
- **When to use:** keynote-style minimalist talk where you have something to say and not much to show; one idea per slide; talks / launches / public presentations.
|
||||||
|
- **Path:** `templates/full-decks/dir-key-nav-minimal/index.html`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Scenario decks (generic, reusable)
|
||||||
|
|
||||||
|
These are not extracted from a single source — they are generic scaffolds for the most common presentation jobs. Each is visually distinctive and content-rich out of the box.
|
||||||
|
|
||||||
|
| # | Name | Slides | Feel | When to use |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 9 | `pitch-deck` | 10 | White + blue→purple gradient, YC/VC vibe, big numbers, traction chart | Fundraising, startup pitch, investor meeting |
|
||||||
|
| 10 | `product-launch` | 8 | Dark hero + light content, warm orange→peach, feature cards, pricing tiers, CTA | Announcing a product, launch keynote |
|
||||||
|
| 11 | `tech-sharing` | 8 | GitHub-dark, JetBrains Mono, terminal code blocks, agenda + Q&A | 技术分享, internal tech talk, conference talk |
|
||||||
|
| 12 | `weekly-report` | 7 | Corporate clarity, 8-cell KPI grid, shipped list, 8-week bar chart, next-week table | 周报, team status update, business review |
|
||||||
|
| 13 | `xhs-post` | 9 | **3:4 @ 810×1080**, warm pastel, dashed sticker cards, page dots | 小红书 图文 post, Instagram carousel |
|
||||||
|
| 14 | `course-module` | 7 | Warm paper + Playfair serif, persistent left sidebar of learning objectives, MCQ self-check | 教学模块, online course, workshop module |
|
||||||
|
|
||||||
|
Each folder: `index.html`, scoped `style.css` (prefixed `.tpl-<name>`), `README.md`. The `xhs-post` template overrides the default `.slide` box to fixed `810×1080` for 3:4 portrait.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Authoring notes
|
||||||
|
|
||||||
|
- Every template scopes its CSS under `.tpl-<name>` so two or more templates can load on the same page without collisions.
|
||||||
|
- Swap demo content, but keep the structural classes — they are what gives each template its identity.
|
||||||
|
- The shared runtime (`assets/runtime.js`) provides keyboard nav, fullscreen, overview grid, theme cycling — you don't need to add any JS.
|
||||||
|
- Charts are hand-rolled SVG (no CDN dependency). Feel free to replace with chart.js / echarts if you need interactive data.
|
||||||
|
|
@ -59,6 +59,35 @@ All themes define the same variables: `--bg`, `--bg-soft`, `--surface`,
|
||||||
| `blueprint` | 蓝图工程 + 网格底纹 + 蒙太奇字体。 | 系统架构、工程蓝图 |
|
| `blueprint` | 蓝图工程 + 网格底纹 + 蒙太奇字体。 | 系统架构、工程蓝图 |
|
||||||
| `terminal-green` | 绿屏终端 + 等宽 + 发光文字。 | CLI/black-hat/复古朋克 |
|
| `terminal-green` | 绿屏终端 + 等宽 + 发光文字。 | CLI/black-hat/复古朋克 |
|
||||||
|
|
||||||
|
## v2 additions
|
||||||
|
|
||||||
|
### Light & professional
|
||||||
|
|
||||||
|
| name | description | when to use |
|
||||||
|
|---|---|---|
|
||||||
|
| `corporate-clean` | 纯白 + 海军蓝 accent + Inter + 保守边框。 | 董事会汇报、B2B 销售、金融保险 |
|
||||||
|
| `pitch-deck-vc` | YC 风白底 + 蓝紫渐变 accent + 大留白。 | 融资路演、种子轮、VC meeting |
|
||||||
|
| `academic-paper` | 论文白 + 衬线正文 + 黑墨 + 蓝链接。 | 学术报告、研究分享、会议论文 |
|
||||||
|
| `japanese-minimal` | 象牙白 + 朱红 accent + 极大留白 + Noto Serif。 | 品牌升级、匠人故事、禅意叙事 |
|
||||||
|
| `engineering-whiteprint` | 白底 + 坐标纸网格 + 海军墨线 + 等宽字。 | 系统设计、API 文档、架构白皮书 |
|
||||||
|
|
||||||
|
### Bold & editorial
|
||||||
|
|
||||||
|
| name | description | when to use |
|
||||||
|
|---|---|---|
|
||||||
|
| `magazine-bold` | 奶油底 + 超大 Playfair 衬线 + 橙色 spot。 | 专栏文章、封面故事、品牌月刊 |
|
||||||
|
| `news-broadcast` | 白底 + 红色竖条 + Oswald 大写 + 硬阴影。 | 突发新闻、发布通稿、数据播报 |
|
||||||
|
| `midcentury` | 奶油底 + 芥末/青/焦橙 + 锐利几何。 | 设计史、家居美学、复古品牌 |
|
||||||
|
| `retro-tv` | 暖奶油 + CRT 扫描线 + 琥珀橙 accent。 | 怀旧叙事、八零九零年代主题 |
|
||||||
|
|
||||||
|
### Effect-heavy / dramatic
|
||||||
|
|
||||||
|
| name | description | when to use |
|
||||||
|
|---|---|---|
|
||||||
|
| `cyberpunk-neon` | 纯黑 + 霓虹粉青黄 + 发光 + JetBrains Mono。 | 黑客、地下文化、赛博 talk |
|
||||||
|
| `vaporwave` | 深紫 + 粉红青蓝渐变 + 晕染光斑。 | 音乐、潮流艺术、A E S T H E T I C |
|
||||||
|
| `y2k-chrome` | 银铬渐变 + 彩虹 accent + 大圆角 + Space Grotesk。 | 千禧怀旧、时尚品牌、Gen-Z |
|
||||||
|
|
||||||
## How to apply
|
## How to apply
|
||||||
|
|
||||||
```html
|
```html
|
||||||
|
|
|
||||||
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 316 KiB |
|
After Width: | Height: | Size: 120 KiB |
|
After Width: | Height: | Size: 140 KiB |
|
After Width: | Height: | Size: 207 KiB |
|
After Width: | Height: | Size: 115 KiB |
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 63 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 170 KiB |
|
After Width: | Height: | Size: 248 KiB |
|
After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 190 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 89 KiB |
|
After Width: | Height: | Size: 79 KiB |
|
After Width: | Height: | Size: 95 KiB |
|
After Width: | Height: | Size: 91 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 84 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 82 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 81 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 79 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 119 KiB |
|
After Width: | Height: | Size: 82 KiB |
|
After Width: | Height: | Size: 82 KiB |
|
After Width: | Height: | Size: 82 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 152 KiB |
|
After Width: | Height: | Size: 95 KiB |
|
After Width: | Height: | Size: 91 KiB |
|
After Width: | Height: | Size: 77 KiB |
|
After Width: | Height: | Size: 120 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 82 KiB |
|
After Width: | Height: | Size: 74 KiB |
|
After Width: | Height: | Size: 77 KiB |
|
After Width: | Height: | Size: 83 KiB |
|
After Width: | Height: | Size: 102 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
|
@ -1,63 +1,172 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="zh-CN" data-theme="aurora">
|
<html lang="en" data-theme="aurora">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8"><title>Animation Showcase — html-ppt</title>
|
<meta charset="utf-8"><title>Animation + FX Showcase — html-ppt</title>
|
||||||
<link rel="stylesheet" href="../assets/fonts.css">
|
<link rel="stylesheet" href="../assets/fonts.css">
|
||||||
<link rel="stylesheet" href="../assets/base.css">
|
<link rel="stylesheet" href="../assets/base.css">
|
||||||
<link rel="stylesheet" id="theme-link" href="../assets/themes/aurora.css">
|
<link rel="stylesheet" id="theme-link" href="../assets/themes/aurora.css">
|
||||||
<link rel="stylesheet" href="../assets/animations/animations.css">
|
<link rel="stylesheet" href="../assets/animations/animations.css">
|
||||||
<style>
|
<style>
|
||||||
.anim-box{margin:24px auto 0;width:640px;height:240px;border-radius:var(--radius-lg);background:var(--grad-soft);display:flex;align-items:center;justify-content:center;font-size:44px;font-weight:800;color:var(--text-1);box-shadow:var(--shadow-lg);border:1px solid var(--border)}
|
.fx-stage{
|
||||||
.anim-indicator{position:absolute;top:24px;right:40px;font-family:var(--font-mono);font-size:11px;color:var(--text-3);letter-spacing:.1em}
|
position:relative;
|
||||||
|
margin:20px auto 0;
|
||||||
|
width:min(900px, 92%);
|
||||||
|
height:380px;
|
||||||
|
border-radius:var(--radius-lg, 16px);
|
||||||
|
background:rgba(10,12,22,0.55);
|
||||||
|
border:1px solid var(--border,#2a2a3a);
|
||||||
|
box-shadow:var(--shadow-lg, 0 20px 60px rgba(0,0,0,.4));
|
||||||
|
overflow:hidden;
|
||||||
|
}
|
||||||
|
.fx-label{
|
||||||
|
position:absolute;top:14px;left:16px;z-index:5;
|
||||||
|
font-family:var(--font-mono, ui-monospace,Menlo,monospace);
|
||||||
|
font-size:11px;letter-spacing:.14em;color:var(--text-3,#a0a0b8);
|
||||||
|
text-transform:uppercase;
|
||||||
|
background:rgba(0,0,0,0.35);padding:4px 10px;border-radius:999px;
|
||||||
|
border:1px solid rgba(255,255,255,0.08);
|
||||||
|
}
|
||||||
|
.fx-replay{
|
||||||
|
position:absolute;top:12px;right:14px;z-index:5;
|
||||||
|
appearance:none;cursor:pointer;
|
||||||
|
padding:7px 14px;border-radius:999px;
|
||||||
|
background:linear-gradient(135deg, var(--accent,#7c5cff), var(--accent-2,#22d3ee));
|
||||||
|
color:#fff;font:600 12px system-ui,sans-serif;
|
||||||
|
border:0;letter-spacing:.05em;
|
||||||
|
box-shadow:0 4px 16px rgba(124,92,255,0.35);
|
||||||
|
}
|
||||||
|
.fx-replay:hover{ filter:brightness(1.1); transform:translateY(-1px); }
|
||||||
|
.fx-indicator{position:absolute;top:24px;right:40px;font-family:var(--font-mono);font-size:11px;color:var(--text-3);letter-spacing:.1em}
|
||||||
|
.anim-box{margin:24px auto 0;width:640px;height:240px;border-radius:var(--radius-lg);background:var(--grad-soft);display:flex;align-items:center;justify-content:center;font-size:44px;font-weight:800;color:var(--text-1);box-shadow:var(--shadow-lg);border:1px solid var(--border)}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="deck"></div>
|
<div class="deck"></div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const list=[
|
const FX = [
|
||||||
['fade-up','从下向上淡入。默认的万能入场。'],
|
['particle-burst', 'Particles explode from center, gravity + fade, re-bursts every ~2.5s.'],
|
||||||
['fade-down','从上向下淡入。标题行常用。'],
|
['confetti-cannon', 'Colored rotating rects arcing from both bottom corners.'],
|
||||||
['fade-left','从左侧水平滑入。'],
|
['firework', 'Rockets launch from the bottom and burst into colored sparks.'],
|
||||||
['fade-right','从右侧水平滑入。'],
|
['starfield', '3D perspective starfield — infinite flythrough.'],
|
||||||
['rise-in','带轻微模糊从下方升起。高级感入场。'],
|
['matrix-rain', 'Classic green katakana + hex columns raining down.'],
|
||||||
['drop-in','从上方落下。强调感。'],
|
['knowledge-graph', 'Force-directed graph, 28 labeled nodes, live physics + springs.'],
|
||||||
['zoom-pop','弹性缩放,先过 1.04 再回到 1。按钮/数字常用。'],
|
['neural-net', '4-6-6-3 feedforward net with pulses traveling along edges.'],
|
||||||
['blur-in','去模糊聚焦。封面推荐。'],
|
['constellation', 'Drifting points connect when close — ambient background.'],
|
||||||
['glitch-in','故障艺术抖动入场。'],
|
['orbit-ring', '5 concentric rings with dots rotating at different speeds.'],
|
||||||
['typewriter','打字机逐字显示。'],
|
['galaxy-swirl', 'Logarithmic spiral with ~800 particles.'],
|
||||||
['neon-glow','文字周期性发光。霓虹主题推荐。'],
|
['word-cascade', 'Words fall from top and pile up at the bottom.'],
|
||||||
['shimmer-sweep','表面反光扫过。金属质感。'],
|
['letter-explode', 'Letters fly in from random directions, loops.'],
|
||||||
['gradient-flow','渐变文字水平流动。'],
|
['chain-react', 'Row of 8 circles — a wave pulse dominoes across them.'],
|
||||||
['stagger-list','子元素错峰出现。'],
|
['magnetic-field', 'Particles follow sine curves leaving gradient trails.'],
|
||||||
['counter-up','数字从 0 tick 到目标值。KPI 必备。'],
|
['data-stream', 'Rows of scrolling hex/binary text, cyberpunk feel.'],
|
||||||
['path-draw','SVG 线条勾勒动效。'],
|
['gradient-blob', '4 drifting blurred radial gradients (additive blending).'],
|
||||||
['parallax-tilt','鼠标悬浮时 3D 倾斜。'],
|
['sparkle-trail', 'Sparkles emit at your cursor (auto-wiggles if idle).'],
|
||||||
['card-flip-3d','卡片 Y 轴翻转。'],
|
['shockwave', 'Expanding rings emanating from center, looping.'],
|
||||||
['cube-rotate-3d','立方体旋转入场。'],
|
['typewriter-multi', 'Three lines typing concurrently with blinking block cursors.'],
|
||||||
['page-turn-3d','页面翻页效果。'],
|
['counter-explosion', 'Number counts 0 → 2400, bursts particles, resets.']
|
||||||
['perspective-zoom','远景拉近。'],
|
|
||||||
['marquee-scroll','跑马灯横向滚动。'],
|
|
||||||
['kenburns','Ken Burns 缓慢推拉。图片/封面必备。'],
|
|
||||||
['confetti-burst','彩纸爆炸。成就 / 结语页。'],
|
|
||||||
['spotlight','聚光灯圆形揭示。'],
|
|
||||||
['morph-shape','SVG 形状变形。'],
|
|
||||||
['ripple-reveal','从一角涟漪展开。']
|
|
||||||
];
|
];
|
||||||
const deck=document.querySelector('.deck');
|
|
||||||
list.forEach((a,i)=>{
|
const CSS_ANIMS = [
|
||||||
const s=document.createElement('section');
|
['fade-up','Translate from +32 px, fade.'],
|
||||||
s.className='slide';s.setAttribute('data-title',a[0]);
|
['fade-down','Translate from -32 px, fade.'],
|
||||||
s.innerHTML=`
|
['fade-left','From left.'],
|
||||||
<span class="anim-indicator">${i+1}/${list.length}</span>
|
['fade-right','From right.'],
|
||||||
<p class="kicker">Animation · ${String(i+1).padStart(2,'0')}</p>
|
['rise-in','Rise + blur-off.'],
|
||||||
|
['drop-in','Drop from above.'],
|
||||||
|
['zoom-pop','Elastic scale pop.'],
|
||||||
|
['blur-in','Blur clears.'],
|
||||||
|
['glitch-in','Glitch jitter.'],
|
||||||
|
['typewriter','Typewriter reveal.'],
|
||||||
|
['neon-glow','Neon pulse.'],
|
||||||
|
['shimmer-sweep','Sheen sweep.'],
|
||||||
|
['gradient-flow','Gradient flow.'],
|
||||||
|
['stagger-list','Staggered children.'],
|
||||||
|
['counter-up','Number tick.'],
|
||||||
|
['path-draw','SVG strokes draw.'],
|
||||||
|
['parallax-tilt','3D hover tilt.'],
|
||||||
|
['card-flip-3d','Y-axis flip.'],
|
||||||
|
['cube-rotate-3d','Cube rotate.'],
|
||||||
|
['page-turn-3d','Page turn.'],
|
||||||
|
['perspective-zoom','Pull from -400 Z.'],
|
||||||
|
['marquee-scroll','Infinite marquee.'],
|
||||||
|
['kenburns','Ken Burns zoom.'],
|
||||||
|
['confetti-burst','Pseudo confetti.'],
|
||||||
|
['spotlight','Circular clip reveal.'],
|
||||||
|
['morph-shape','SVG d morph.'],
|
||||||
|
['ripple-reveal','Corner ripple.']
|
||||||
|
];
|
||||||
|
|
||||||
|
const deck = document.querySelector('.deck');
|
||||||
|
const total = FX.length + CSS_ANIMS.length;
|
||||||
|
|
||||||
|
// Build FX slides (1..20)
|
||||||
|
FX.forEach((f, i) => {
|
||||||
|
const idx = i + 1;
|
||||||
|
const [name, desc] = f;
|
||||||
|
const sec = document.createElement('section');
|
||||||
|
sec.className = 'slide';
|
||||||
|
sec.setAttribute('data-title', 'fx / ' + name);
|
||||||
|
|
||||||
|
const extraAttrs = name === 'letter-explode'
|
||||||
|
? 'data-fx-text-value="' + name.toUpperCase() + '"'
|
||||||
|
: name === 'counter-explosion'
|
||||||
|
? 'data-fx-to="2400"'
|
||||||
|
: name === 'typewriter-multi'
|
||||||
|
? 'data-fx-line1="> initializing knowledge graph..." data-fx-line2="> loading 28 concept nodes" data-fx-line3="> agent ready. awaiting prompt_"'
|
||||||
|
: '';
|
||||||
|
|
||||||
|
sec.innerHTML = `
|
||||||
|
<span class="fx-indicator">${idx}/${total}</span>
|
||||||
|
<p class="kicker">FX · canvas · ${String(idx).padStart(2,'0')}</p>
|
||||||
|
<h1 class="h1"><span class="gradient-text">${name}</span></h1>
|
||||||
|
<p class="lede">${desc}</p>
|
||||||
|
<div class="fx-stage">
|
||||||
|
<span class="fx-label">data-fx="${name}"</span>
|
||||||
|
<button class="fx-replay" type="button">Replay</button>
|
||||||
|
<div class="fx-host" style="position:absolute;inset:0;" data-fx="${name}" ${extraAttrs}></div>
|
||||||
|
</div>
|
||||||
|
<div class="deck-footer"><span class="dim2">Press → for next slide</span><span class="slide-number" data-current="${idx}" data-total="${total}"></span></div>
|
||||||
|
`;
|
||||||
|
deck.appendChild(sec);
|
||||||
|
|
||||||
|
// Wire Replay button
|
||||||
|
sec.querySelector('.fx-replay').addEventListener('click', () => {
|
||||||
|
const host = sec.querySelector('.fx-host');
|
||||||
|
const name2 = host.getAttribute('data-fx');
|
||||||
|
const attrs = {};
|
||||||
|
for (const a of host.attributes) attrs[a.name] = a.value;
|
||||||
|
const parent = host.parentNode;
|
||||||
|
// stop existing
|
||||||
|
if (window.__hpxActive && window.__hpxActive.has(host)){
|
||||||
|
try{ window.__hpxActive.get(host).stop(); }catch(e){}
|
||||||
|
window.__hpxActive.delete(host);
|
||||||
|
}
|
||||||
|
const fresh = document.createElement('div');
|
||||||
|
for (const k in attrs) fresh.setAttribute(k, attrs[k]);
|
||||||
|
fresh.style.cssText = host.style.cssText;
|
||||||
|
parent.replaceChild(fresh, host);
|
||||||
|
if (window.__hpxReinit) window.__hpxReinit(sec);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Build CSS animation slides (legacy, kept for completeness)
|
||||||
|
CSS_ANIMS.forEach((a, i) => {
|
||||||
|
const idx = FX.length + i + 1;
|
||||||
|
const sec = document.createElement('section');
|
||||||
|
sec.className = 'slide';
|
||||||
|
sec.setAttribute('data-title', a[0]);
|
||||||
|
sec.innerHTML = `
|
||||||
|
<span class="fx-indicator">${idx}/${total}</span>
|
||||||
|
<p class="kicker">CSS anim · ${String(idx).padStart(2,'0')}</p>
|
||||||
<h1 class="h1"><span class="gradient-text">${a[0]}</span></h1>
|
<h1 class="h1"><span class="gradient-text">${a[0]}</span></h1>
|
||||||
<p class="lede">${a[1]}</p>
|
<p class="lede">${a[1]}</p>
|
||||||
<div class="anim-box anim-${a[0]}" data-anim="${a[0]}" data-anim-target>${a[0]}</div>
|
<div class="anim-box anim-${a[0]}" data-anim="${a[0]}" data-anim-target>${a[0]}</div>
|
||||||
<div class="deck-footer"><span class="dim2">按 A 循环所有动效</span><span class="slide-number" data-current="${i+1}" data-total="${list.length}"></span></div>
|
<div class="deck-footer"><span class="dim2">Press A to cycle</span><span class="slide-number" data-current="${idx}" data-total="${total}"></span></div>
|
||||||
<div class="notes">每次切换到这张 slide,runtime 会自动重新触发 [data-anim] 元素的入场动画。</div>
|
|
||||||
`;
|
`;
|
||||||
deck.appendChild(s);
|
deck.appendChild(sec);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<script src="../assets/runtime.js"></script>
|
<script src="../assets/runtime.js"></script>
|
||||||
|
<script src="../assets/animations/fx-runtime.js"></script>
|
||||||
</body></html>
|
</body></html>
|
||||||
|
|
|
||||||
|
After Width: | Height: | Size: 54 KiB |
|
|
@ -0,0 +1,81 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8"><title>Full-Deck Gallery — html-ppt v2</title>
|
||||||
|
<link rel="stylesheet" href="../assets/fonts.css">
|
||||||
|
<link rel="stylesheet" href="../assets/base.css">
|
||||||
|
<style>
|
||||||
|
html,body{background:#0b0c10;color:#e8ebf4;font-family:var(--font-sans)}
|
||||||
|
.deck{background:#0b0c10}
|
||||||
|
.slide{padding:60px 80px;color:#e8ebf4;background:transparent;display:flex;flex-direction:column}
|
||||||
|
.slide h1{color:#fff;font-size:48px;margin:0 0 6px;letter-spacing:-.02em}
|
||||||
|
.slide .sub{color:#aab0c0;font-size:18px;margin:0 0 22px}
|
||||||
|
.frame-wrap{flex:1;border-radius:14px;overflow:hidden;border:1px solid rgba(255,255,255,.12);
|
||||||
|
box-shadow:0 30px 80px rgba(0,0,0,.5);position:relative;background:#fff}
|
||||||
|
iframe.tpl{position:absolute;inset:0;width:200%;height:200%;border:0;
|
||||||
|
transform:scale(.5);transform-origin:top left}
|
||||||
|
.meta{position:absolute;top:24px;right:40px;font-family:'JetBrains Mono',monospace;
|
||||||
|
font-size:12px;color:#6a7086;letter-spacing:.14em;text-transform:uppercase;z-index:30}
|
||||||
|
.tag{display:inline-block;padding:4px 10px;border-radius:999px;background:rgba(255,255,255,.08);
|
||||||
|
color:#cfd3dc;font-size:11px;margin-right:6px}
|
||||||
|
.cover{align-items:center;justify-content:center;text-align:center}
|
||||||
|
.cover h1{font-size:84px;background:linear-gradient(135deg,#60a5fa,#a78bfa,#f472b6);
|
||||||
|
-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}
|
||||||
|
.cover p{color:#aab0c0;max-width:60ch;font-size:20px}
|
||||||
|
.legend{display:flex;gap:10px;flex-wrap:wrap;margin-top:18px}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="deck">
|
||||||
|
|
||||||
|
<section class="slide cover">
|
||||||
|
<p class="kicker" style="color:#a78bfa">HTML-PPT v2 · Full-Deck Gallery</p>
|
||||||
|
<h1>14 full-deck templates</h1>
|
||||||
|
<p>Press → to browse. Each slide is a live iframe preview of a complete, multi-slide deck template. Open any <code>templates/full-decks/<name>/index.html</code> to see the full deck, or copy the folder to scaffold your own.</p>
|
||||||
|
<div class="legend">
|
||||||
|
<span class="tag">8 extracted from real decks</span>
|
||||||
|
<span class="tag">6 scenario scaffolds</span>
|
||||||
|
<span class="tag">scoped .tpl-<name> CSS</span>
|
||||||
|
<span class="tag">36 themes compatible</span>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Template preview slides generated via JS below -->
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const TPLS = [
|
||||||
|
['xhs-white-editorial', '白底杂志风', 'extracted', 'xhs posts, editorial lifestyle'],
|
||||||
|
['graphify-dark-graph', '暗底知识图谱', 'extracted', 'AI/graph/data products'],
|
||||||
|
['knowledge-arch-blueprint', '奶油蓝图架构', 'extracted', 'architecture, systems thinking'],
|
||||||
|
['hermes-cyber-terminal', '暗终端 cyber', 'extracted', 'devtool, honest-review, agent demos'],
|
||||||
|
['obsidian-claude-gradient', 'GitHub 暗紫渐变', 'extracted', 'tool walkthroughs, LLM product'],
|
||||||
|
['testing-safety-alert', '红琥珀警示', 'extracted', 'security, incident review, AI safety'],
|
||||||
|
['xhs-pastel-card', '柔和马卡龙', 'extracted', 'lifestyle, soft emotional'],
|
||||||
|
['dir-key-nav-minimal', '方向键 8 色极简', 'extracted', 'keynote, one-idea-per-slide'],
|
||||||
|
['pitch-deck', 'Pitch Deck YC 风', 'scenario', 'fundraising, startup pitch'],
|
||||||
|
['product-launch', 'Product Launch', 'scenario', 'product announcement, launch keynote'],
|
||||||
|
['tech-sharing', 'Tech Sharing 技术分享','scenario','internal tech talk, conference talk'],
|
||||||
|
['weekly-report', 'Weekly Report 周报','scenario', 'status update, business review'],
|
||||||
|
['xhs-post', '小红书 图文 9 屏 3:4','scenario', 'xiaohongshu / ig carousel'],
|
||||||
|
['course-module', 'Course Module 教学模块','scenario','online course, workshop module']
|
||||||
|
];
|
||||||
|
|
||||||
|
const deck = document.querySelector('.deck');
|
||||||
|
TPLS.forEach((t,i)=>{
|
||||||
|
const s = document.createElement('section');
|
||||||
|
s.className = 'slide';
|
||||||
|
s.setAttribute('data-title',t[0]);
|
||||||
|
s.innerHTML = `
|
||||||
|
<span class="meta">${i+1}/${TPLS.length+1}</span>
|
||||||
|
<h1>${t[0]}</h1>
|
||||||
|
<p class="sub">${t[1]} · <span class="tag">${t[2]}</span> ${t[3]}</p>
|
||||||
|
<div class="frame-wrap">
|
||||||
|
<iframe class="tpl" src="full-decks/${t[0]}/index.html" loading="eager" title="${t[0]}"></iframe>
|
||||||
|
</div>`;
|
||||||
|
deck.appendChild(s);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<script src="../assets/runtime.js"></script>
|
||||||
|
</body></html>
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
# course-module · 教学模块
|
||||||
|
|
||||||
|
7-slide teaching module: cover (title + meta), objectives, core concept, worked example, exercise, check-your-understanding (MCQ), summary.
|
||||||
|
|
||||||
|
Academic but friendly look: warm off-white paper, Playfair Display display type, a green/terracotta accent pair. A persistent **left sidebar** on content slides lists the module's learning objectives and checks them off as you progress — students always know where they are.
|
||||||
|
|
||||||
|
**Use when:** online course modules, lecture handouts, onboarding curricula, workshop units.
|
||||||
|
**Feel:** a good textbook opened to a chapter — structured, quiet, encouraging.
|
||||||