ismail c5f76b3855
Some checks are pending
Build and Push Docker Image / build (push) Waiting to run
updates
2026-05-11 14:45:30 +03:00

238 lines
8.0 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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