chore: ruler files update
Signed-off-by: Dmytro Stanchiev <git@dmytros.dev>
This commit is contained in:
237
.claude/skills/huashu-design/assets/deck_index.html
Normal file
237
.claude/skills/huashu-design/assets/deck_index.html
Normal file
@@ -0,0 +1,237 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Deck · Multi-file Slide Index</title>
|
||||
<!--
|
||||
deck_index.html — 多文件 slide deck 的拼接器
|
||||
|
||||
配合「每页一个独立 HTML」架构使用。与单文件 deck_stage.js 对比:
|
||||
· 每页独立作用域(CSS/JS 都隔离),一页出 bug 不影响其他页
|
||||
· 单页可直接在浏览器打开验证,不依赖 JS goTo()
|
||||
· 多 agent 可并行做不同页,merge 时零冲突
|
||||
· 适合 ≥15 页的讲座/课件/长 deck
|
||||
|
||||
用法:
|
||||
1. 把本文件复制到 deck 根目录,重命名 index.html
|
||||
2. 在同目录建 slides/ 子目录,放每一页独立 HTML
|
||||
3. 编辑下方 MANIFEST 数组,按顺序列出文件名和人类可读标签
|
||||
4. 每张 slide HTML 建议尺寸 1920×1080,自带背景/字体;不要依赖外层 CSS
|
||||
|
||||
共享资源(如果需要):
|
||||
· shared/tokens.css — 跨页 CSS 变量(色板/字号)
|
||||
· shared/chrome.html — 页眉页脚可复用片段
|
||||
· 每页 HTML 自己 <link> 进去即可
|
||||
|
||||
键盘:← / → / Space / PgUp / PgDown / Home / End / 1-9 跳页 / P 打印
|
||||
-->
|
||||
|
||||
<!-- ═══════════════════════════════════════════════════════ -->
|
||||
<!-- EDIT THIS — deck 所有页按顺序列出 -->
|
||||
<!-- ═══════════════════════════════════════════════════════ -->
|
||||
<script>
|
||||
window.DECK_MANIFEST = [
|
||||
{ file: "slides/01-cover.html", label: "Cover" },
|
||||
{ file: "slides/02-quote.html", label: "Opening Quote" },
|
||||
{ file: "slides/03-intro.html", label: "Self-intro" },
|
||||
// 继续往下加。file 是相对本文件的路径,label 用于计数器
|
||||
];
|
||||
|
||||
// 固定 canvas 尺寸。每页 HTML 都应该按这个尺寸设计。
|
||||
window.DECK_WIDTH = 1920;
|
||||
window.DECK_HEIGHT = 1080;
|
||||
</script>
|
||||
|
||||
<style>
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
html, body {
|
||||
height: 100%;
|
||||
background: #0a0a0a;
|
||||
overflow: hidden;
|
||||
font-family: -apple-system, "PingFang SC", sans-serif;
|
||||
}
|
||||
#stage {
|
||||
position: fixed;
|
||||
top: 50%; left: 50%;
|
||||
transform-origin: top left;
|
||||
will-change: transform;
|
||||
background: #fff;
|
||||
box-shadow: 0 10px 60px rgba(0,0,0,0.4);
|
||||
/* size set by JS from DECK_WIDTH/HEIGHT */
|
||||
}
|
||||
iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 0;
|
||||
display: block;
|
||||
background: #fff;
|
||||
}
|
||||
.counter {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
background: rgba(0,0,0,0.65);
|
||||
color: #fff;
|
||||
padding: 6px 14px;
|
||||
border-radius: 999px;
|
||||
font-size: 13px;
|
||||
letter-spacing: 0.05em;
|
||||
font-variant-numeric: tabular-nums;
|
||||
z-index: 100;
|
||||
user-select: none;
|
||||
opacity: 0.7;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
.counter:hover { opacity: 1; }
|
||||
.counter .label { color: rgba(255,255,255,0.7); margin-left: 8px; }
|
||||
.nav-zone {
|
||||
position: fixed;
|
||||
top: 0; bottom: 0;
|
||||
width: 15%;
|
||||
cursor: pointer;
|
||||
z-index: 50;
|
||||
}
|
||||
.nav-zone.left { left: 0; }
|
||||
.nav-zone.right { right: 0; }
|
||||
.nav-hint {
|
||||
position: absolute;
|
||||
top: 50%; transform: translateY(-50%);
|
||||
width: 44px; height: 44px;
|
||||
border-radius: 999px;
|
||||
background: rgba(255,255,255,0.08);
|
||||
color: rgba(255,255,255,0.6);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 22px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
.nav-zone.left .nav-hint { left: 20px; }
|
||||
.nav-zone.right .nav-hint { right: 20px; }
|
||||
.nav-zone:hover .nav-hint { opacity: 1; }
|
||||
|
||||
/* Print: one slide per page, no navigation UI */
|
||||
@media print {
|
||||
@page { size: 1920px 1080px; margin: 0; }
|
||||
html, body { background: #fff; overflow: visible; height: auto; }
|
||||
#stage { position: static; transform: none !important; box-shadow: none; }
|
||||
.counter, .nav-zone { display: none !important; }
|
||||
/* In print mode we render all slides sequentially — see JS */
|
||||
.print-stack { display: block; }
|
||||
.print-stack iframe {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
page-break-after: always;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="stage">
|
||||
<iframe id="frame" src="about:blank"></iframe>
|
||||
</div>
|
||||
|
||||
<div class="nav-zone left" id="navL"><div class="nav-hint">‹</div></div>
|
||||
<div class="nav-zone right" id="navR"><div class="nav-hint">›</div></div>
|
||||
<div class="counter" id="counter">1 / 1</div>
|
||||
|
||||
<!-- Print-only stack: populated on beforeprint, stripped on afterprint -->
|
||||
<div class="print-stack" id="printStack" style="display:none;"></div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
const W = window.DECK_WIDTH || 1920;
|
||||
const H = window.DECK_HEIGHT || 1080;
|
||||
const deck = window.DECK_MANIFEST || [];
|
||||
const stage = document.getElementById('stage');
|
||||
const frame = document.getElementById('frame');
|
||||
const counter = document.getElementById('counter');
|
||||
const printStack = document.getElementById('printStack');
|
||||
const storageKey = 'deck-index-' + location.pathname;
|
||||
let current = 0;
|
||||
|
||||
stage.style.width = W + 'px';
|
||||
stage.style.height = H + 'px';
|
||||
|
||||
function fit() {
|
||||
const s = Math.min(window.innerWidth / W, window.innerHeight / H);
|
||||
const x = (window.innerWidth - W * s) / 2;
|
||||
const y = (window.innerHeight - H * s) / 2;
|
||||
stage.style.transform = `translate(${x}px, ${y}px) scale(${s})`;
|
||||
stage.style.top = '0';
|
||||
stage.style.left = '0';
|
||||
}
|
||||
|
||||
function show(idx) {
|
||||
if (idx < 0 || idx >= deck.length) return;
|
||||
current = idx;
|
||||
frame.src = deck[idx].file;
|
||||
counter.innerHTML = `${idx + 1} / ${deck.length} <span class="label">${deck[idx].label || ''}</span>`;
|
||||
try { localStorage.setItem(storageKey, String(idx)); } catch (_) {}
|
||||
if (location.hash !== '#' + (idx + 1)) {
|
||||
history.replaceState(null, '', '#' + (idx + 1));
|
||||
}
|
||||
}
|
||||
|
||||
function next() { show(Math.min(current + 1, deck.length - 1)); }
|
||||
function prev() { show(Math.max(current - 1, 0)); }
|
||||
|
||||
// Keyboard
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
|
||||
switch (e.key) {
|
||||
case 'ArrowRight': case ' ': case 'PageDown': e.preventDefault(); next(); break;
|
||||
case 'ArrowLeft': case 'PageUp': e.preventDefault(); prev(); break;
|
||||
case 'Home': e.preventDefault(); show(0); break;
|
||||
case 'End': e.preventDefault(); show(deck.length - 1); break;
|
||||
case 'p': case 'P': window.print(); break;
|
||||
default:
|
||||
if (e.key >= '1' && e.key <= '9') {
|
||||
const i = parseInt(e.key, 10) - 1;
|
||||
if (i < deck.length) { e.preventDefault(); show(i); }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('navL').addEventListener('click', prev);
|
||||
document.getElementById('navR').addEventListener('click', next);
|
||||
window.addEventListener('resize', fit);
|
||||
window.addEventListener('hashchange', () => {
|
||||
const m = location.hash.match(/^#(\d+)$/);
|
||||
if (m) show(parseInt(m[1], 10) - 1);
|
||||
});
|
||||
|
||||
// Initial: hash > localStorage > 0
|
||||
const hashMatch = location.hash.match(/^#(\d+)$/);
|
||||
if (hashMatch) current = Math.min(parseInt(hashMatch[1], 10) - 1, deck.length - 1);
|
||||
else try {
|
||||
const v = parseInt(localStorage.getItem(storageKey), 10);
|
||||
if (!isNaN(v) && v >= 0 && v < deck.length) current = v;
|
||||
} catch (_) {}
|
||||
fit();
|
||||
show(current);
|
||||
|
||||
// Print: build a stack of all iframes so browser prints every slide
|
||||
window.addEventListener('beforeprint', () => {
|
||||
printStack.innerHTML = '';
|
||||
deck.forEach(item => {
|
||||
const f = document.createElement('iframe');
|
||||
f.src = item.file;
|
||||
printStack.appendChild(f);
|
||||
});
|
||||
printStack.style.display = 'block';
|
||||
document.getElementById('stage').style.display = 'none';
|
||||
});
|
||||
window.addEventListener('afterprint', () => {
|
||||
printStack.innerHTML = '';
|
||||
printStack.style.display = 'none';
|
||||
document.getElementById('stage').style.display = '';
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user