483 lines
18 KiB
HTML
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>
|