chore: ruler files update

Signed-off-by: Dmytro Stanchiev <git@dmytros.dev>
This commit is contained in:
2026-05-24 21:03:49 -04:00
parent 97b3ddd653
commit abb472c83d
303 changed files with 46670 additions and 25369 deletions

View 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>