483 lines
18 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{SLIDE_TITLE}}</title>
<style>
/* ============================================================
Slide 尺寸(固定值,不受主题替换影响)
============================================================ */
:root {
--slide-width: 1280px;
--slide-height: 720px;
}
/* ============================================================
主题变量(由 Agent 注入 — 替换 THEME_VARS_START 到 THEME_VARS_END 之间的内容)
============================================================ */
/* THEME_VARS_START */
:root {
/* --- 配色 --- */
--color-primary: #2563eb;
--color-secondary: #64748b;
--color-accent: #f59e0b;
--color-bg: #ffffff;
--color-bg-alt: #f8fafc;
--color-surface: #ffffff;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-text-on-primary: #ffffff;
--color-border: #e2e8f0;
/* --- 字体 --- */
--font-heading: 'Inter', system-ui, -apple-system, sans-serif;
--font-body: 'Inter', system-ui, -apple-system, sans-serif;
--font-mono: 'Fira Code', 'Cascadia Code', monospace;
--font-size-title: 3.5rem;
--font-size-heading: 2.5rem;
--font-size-subheading: 1.5rem;
--font-size-body: 1.125rem;
--font-size-small: 0.875rem;
/* --- 间距和尺寸 --- */
--slide-padding: 4rem;
--spacing-xs: 0.5rem;
--spacing-sm: 1rem;
--spacing-md: 2rem;
--spacing-lg: 3rem;
--spacing-xl: 4rem;
/* --- 效果 --- */
--border-radius: 8px;
--shadow-sm: 0 1px 3px rgba(0,0,0,0.1);
--shadow-md: 0 4px 12px rgba(0,0,0,0.1);
--shadow-lg: 0 8px 24px rgba(0,0,0,0.12);
--transition-speed: 0.3s;
--transition-easing: ease-out;
/* --- 玻璃/渐变扩展(非玻璃主题保持默认值,零渲染开销) --- */
--surface-blur: 0px;
--surface-saturate: 100%;
--bg-pattern: none;
}
/* THEME_VARS_END */
/* ============================================================
基础重置(Skill 提供,不可修改)
============================================================ */
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
html {
font-size: 16px; /* 固定基准:1rem = 16px,主题通过 rem 变量控制实际字号 */
}
body {
width: 100%; height: 100%;
overflow: hidden;
font-family: var(--font-body);
font-size: var(--font-size-body);
color: var(--color-text);
background: var(--color-bg);
-moz-osx-font-smoothing: grayscale;
}
/* ============================================================
Slide 容器 — 16:9 自适应缩放
============================================================ */
.slide-viewport {
position: fixed; inset: 0;
display: flex; align-items: center; justify-content: center;
background: var(--color-bg);
}
.slide-deck {
position: relative;
width: var(--slide-width);
height: var(--slide-height);
transform-origin: center center;
/* overflow 不设 hidden — 允许 .slide.active 的 box-shadow 溢出到视口边缘
各 .slide 自身已有 overflow:hidden 保证内容不外溢 */
}
/* 单页 Slide */
.slide {
position: absolute; inset: 0;
width: var(--slide-width);
height: var(--slide-height);
padding: var(--slide-padding);
--_slide-bg: var(--color-bg); /* 当前页背景色(供 bleed 引用) */
background: var(--_slide-bg);
display: flex; flex-direction: column; justify-content: center;
overflow: hidden;
opacity: 0;
visibility: hidden;
transition: opacity 0.5s var(--transition-easing),
visibility 0.5s var(--transition-easing);
}
.slide.active {
opacity: 1;
visibility: visible;
z-index: 1;
/* 背景无限溢出:消除 slide-deck 边界形成的"大卡片"效果 */
box-shadow: 0 0 0 9999px var(--_slide-bg);
}
/* 奇偶页交替背景(排除特殊布局页面) */
.slide:nth-child(even):not(.slide--title):not(.slide--section):not(.slide--ending) {
--_slide-bg: var(--color-bg-alt);
}
/* ============================================================
通用排版
============================================================ */
.slide h1, .slide h2, .slide h3, .slide h4 {
font-family: var(--font-heading);
line-height: 1.2;
color: var(--color-text);
}
.slide h1 { font-size: min(var(--font-size-title), 60px); text-wrap: balance; margin-bottom: var(--spacing-md); }
.slide h2 { font-size: min(var(--font-size-heading), 48px); text-wrap: balance; margin-bottom: var(--spacing-sm); }
.slide h3 { font-size: var(--font-size-subheading); margin-bottom: var(--spacing-sm); }
.slide p { line-height: 1.7; margin-bottom: var(--spacing-sm); color: var(--color-text-secondary); }
.slide ul, .slide ol { padding-left: 1.5em; margin-bottom: var(--spacing-sm); }
.slide li { line-height: 1.7; margin-bottom: var(--spacing-xs); }
.slide code { font-family: var(--font-mono); font-size: 0.9em; background: var(--color-bg-alt); padding: 0.15em 0.4em; border-radius: var(--border-radius); }
.slide strong { color: var(--color-text); }
.slide a { color: var(--color-primary); text-decoration: none; }
/* ============================================================
右侧圆点导航
============================================================ */
.dot-nav {
position: fixed; right: 20px; top: 50%;
transform: translateY(-50%);
display: flex; flex-direction: column; align-items: center; gap: 10px;
z-index: 100;
opacity: 0.5;
transition: opacity 0.3s ease;
}
.dot-nav:hover { opacity: 1; }
.dot-nav-dot {
width: 10px; height: 10px;
border-radius: 50%;
background: var(--color-text-secondary);
opacity: 0.35;
border: none; padding: 0;
cursor: pointer;
transition: all 0.25s ease;
}
.dot-nav-dot:hover {
opacity: 0.7;
transform: scale(1.3);
}
.dot-nav-dot.active {
opacity: 1;
background: var(--color-primary);
transform: scale(1.4);
}
/* >12 页降级为数字显示 */
.dot-nav-num {
color: var(--color-text-secondary);
font-size: 13px;
font-variant-numeric: tabular-nums;
font-family: var(--font-body);
user-select: none;
opacity: 0.8;
letter-spacing: 0.5px;
}
/* ============================================================
右上角全屏按钮
============================================================ */
.fullscreen-btn {
position: fixed; top: 16px; right: 16px;
width: 36px; height: 36px;
display: flex; align-items: center; justify-content: center;
background: var(--color-border);
border: none; border-radius: var(--border-radius);
color: var(--color-text-secondary);
cursor: pointer;
z-index: 100;
opacity: 0.2;
transition: opacity 0.25s ease, background 0.25s ease, color 0.25s ease;
}
.fullscreen-btn:hover {
opacity: 0.85;
background: var(--color-secondary);
color: var(--color-text-on-primary);
}
/* ============================================================
左右点击翻页区域
============================================================ */
.click-zone {
position: fixed; top: 0; bottom: 0;
width: 20%; z-index: 10; cursor: pointer;
}
.click-zone--prev { left: 0; }
.click-zone--next { right: 0; }
/* ============================================================
玻璃/渐变效果层(主题驱动,非玻璃主题零开销)
============================================================ */
/* 装饰背景层 — 渐变光斑 / 色彩氛围 */
.slide::before {
content: '';
position: absolute;
inset: 0;
background: var(--bg-pattern, none);
pointer-events: none;
}
/* 确保内容在装饰层之上 */
.slide > * {
position: relative;
}
/* 玻璃表面 — backdrop-filter 对所有 surface 容器生效 */
.slide .card,
.slide .comp-side,
.slide .twocol-left,
.slide .twocol-right {
-webkit-backdrop-filter: blur(var(--surface-blur, 0px)) saturate(var(--surface-saturate, 100%));
backdrop-filter: blur(var(--surface-blur, 0px)) saturate(var(--surface-saturate, 100%));
}
/* ============================================================
动画样式占位(由 Agent 按需注入 / 读取 animations.css)
============================================================ */
/* Agent 在此区域注入入场动画样式 */
/* ============================================================
布局样式占位(由 Agent 按需注入)
============================================================ */
/* Agent 在此区域注入各布局类型的样式 */
</style>
</head>
<body>
<div class="slide-viewport">
<div class="slide-deck" id="slideDeck">
<!-- ====================================================
Slide 页面区域(由 Agent 填充)
每页格式:
<section class="slide slide--{layout_type}" data-slide="N">
...内容...
</section>
==================================================== -->
</div><!-- /slide-deck -->
</div><!-- /slide-viewport -->
<!-- 左右点击翻页区域 -->
<div class="click-zone click-zone--prev" id="clickPrev"></div>
<div class="click-zone click-zone--next" id="clickNext"></div>
<!-- 右侧圆点导航 -->
<nav class="dot-nav" id="dotNav"></nav>
<!-- 右上角全屏按钮 -->
<button class="fullscreen-btn" id="btnFullscreen">
<svg width="18" height="18" viewBox="0 0 18 18" fill="none">
<path d="M2 6V3a1 1 0 011-1h3M12 2h3a1 1 0 011 1v3M16 12v3a1 1 0 01-1 1h-3M6 16H3a1 1 0 01-1-1v-3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
<script>
(function () {
'use strict';
/* ============================================================
核心状态
============================================================ */
const deck = document.getElementById('slideDeck');
const slides = () => deck.querySelectorAll('.slide');
const dotNav = document.getElementById('dotNav');
const DOT_THRESHOLD = 12; // 超过此数量时降级为数字显示
let currentIndex = 0;
let totalSlides = 0;
/* ============================================================
初始化
============================================================ */
function init() {
totalSlides = slides().length;
if (totalSlides === 0) return;
buildDotNav();
// 支持 URL hash 导航(如 #5 或 #slide=5)
const hash = window.location.hash.replace('#', '');
const startIndex = parseInt(hash.replace('slide=', ''), 10) - 1;
goTo(startIndex >= 0 && startIndex < totalSlides ? startIndex : 0, false);
fitScale();
// 事件绑定
window.addEventListener('resize', fitScale);
window.addEventListener('keydown', onKeyDown);
document.getElementById('btnFullscreen').addEventListener('click', toggleFullscreen);
document.getElementById('clickPrev').addEventListener('click', prev);
document.getElementById('clickNext').addEventListener('click', next);
// 触控滑动
let touchStartX = 0, touchStartY = 0;
deck.addEventListener('touchstart', e => {
touchStartX = e.touches[0].clientX;
touchStartY = e.touches[0].clientY;
}, { passive: true });
deck.addEventListener('touchend', e => {
const dx = e.changedTouches[0].clientX - touchStartX;
const dy = e.changedTouches[0].clientY - touchStartY;
if (Math.abs(dx) > Math.abs(dy) && Math.abs(dx) > 50) {
dx < 0 ? next() : prev();
}
}, { passive: true });
// 鼠标滚轮 / 触控板滚动翻页
let wheelLocked = false;
document.addEventListener('wheel', e => {
if (wheelLocked) return;
if (Math.abs(e.deltaY) < 15) return;
e.preventDefault();
wheelLocked = true;
e.deltaY > 0 ? next() : prev();
setTimeout(() => { wheelLocked = false; }, 500);
}, { passive: false });
}
/* ============================================================
右侧圆点导航 — 构建
============================================================ */
function buildDotNav() {
dotNav.innerHTML = '';
if (totalSlides <= DOT_THRESHOLD) {
// 圆点模式:每页一个可点击圆点
for (let i = 0; i < totalSlides; i++) {
const dot = document.createElement('button');
dot.className = 'dot-nav-dot';
dot.setAttribute('aria-label', '跳转到第 ' + (i + 1) + ' 页');
dot.addEventListener('click', () => goTo(i));
dotNav.appendChild(dot);
}
} else {
// 数字模式
const num = document.createElement('span');
num.className = 'dot-nav-num';
num.id = 'dotNavNum';
dotNav.appendChild(num);
}
}
function updateDotNav() {
if (totalSlides <= DOT_THRESHOLD) {
const dots = dotNav.querySelectorAll('.dot-nav-dot');
dots.forEach((dot, i) => {
dot.classList.toggle('active', i === currentIndex);
});
} else {
const num = document.getElementById('dotNavNum');
if (num) num.textContent = (currentIndex + 1) + ' / ' + totalSlides;
}
}
/* ============================================================
翻页
============================================================ */
function goTo(index, animate = true) {
if (index < 0 || index >= totalSlides) return;
const allSlides = slides();
allSlides.forEach((s, i) => {
s.classList.toggle('active', i === index);
});
currentIndex = index;
// 同步视口背景色 — 消除 slide-deck 边界可见的"大卡片"效果
requestAnimationFrame(() => {
const bg = getComputedStyle(allSlides[index]).backgroundColor;
document.querySelector('.slide-viewport').style.background = bg;
});
// 触发当前页已注册的动画(Chart.js 延迟初始化 / GSAP 配方等)
// 等待 slide 过渡动画完成(0.5s)+ buffer 后再初始化,避免图表在容器尺寸变化中渲染导致抖动
setTimeout(() => {
if (typeof slideAnimations !== 'undefined' && slideAnimations[currentIndex]) {
slideAnimations[currentIndex]();
}
}, 550);
updateDotNav();
// 同步 URL hash
history.replaceState(null, '', '#' + (index + 1));
}
function next() { goTo(currentIndex + 1); }
function prev() { goTo(currentIndex - 1); }
/* ============================================================
自适应缩放 — 保持 16:9 居中
============================================================ */
function fitScale() {
const vw = window.innerWidth;
const vh = window.innerHeight;
const sw = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--slide-width'));
const sh = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--slide-height'));
const scale = Math.min(vw / sw, vh / sh);
deck.style.transform = 'scale(' + scale + ')';
}
/* ============================================================
键盘导航
============================================================ */
function onKeyDown(e) {
switch (e.key) {
case 'ArrowRight': case 'ArrowDown': case ' ': case 'Enter':
e.preventDefault(); next(); break;
case 'ArrowLeft': case 'ArrowUp':
e.preventDefault(); prev(); break;
case 'Home':
e.preventDefault(); goTo(0); break;
case 'End':
e.preventDefault(); goTo(totalSlides - 1); break;
case 'f': case 'F':
toggleFullscreen(); break;
case 'Escape':
if (document.fullscreenElement) document.exitFullscreen().catch(() => {});
break;
}
}
/* ============================================================
全屏
============================================================ */
function toggleFullscreen() {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen().catch(() => {});
} else {
document.exitFullscreen().catch(() => {});
}
}
/* ============================================================
启动
============================================================ */
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();
</script>
<!-- 可选:GSAP CDN(Agent 按需取消注释)-->
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/gsap.min.js"></script> -->
<!-- 可选:Chart.js CDN(Agent 按需取消注释)-->
<!-- <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.7/dist/chart.umd.min.js"></script> -->
<!-- 可选:ECharts CDN(Agent 按需取消注释)-->
<!-- <script src="https://cdn.jsdelivr.net/npm/echarts@5.5.0/dist/echarts.min.js"></script> -->
</body>
</html>