mirror of
https://git.openapi.site/https://github.com/desirecore/market.git
synced 2026-06-06 04:30:42 +08:00
## 概述 / Summary 新增市场技能 **`guizang-ppt`**(归藏网页 PPT),vendored 自上游开源项目 [op7418/guizang-ppt-skill](https://github.com/op7418/guizang-ppt-skill)。 Add a new **market skill** `guizang-ppt` — generates single-file HTML horizontal-swipe slide decks (web PPT) in two visual systems (editorial "magazine × e-ink" / "Swiss International"). Vendored from the upstream open-source project. ## 变更内容 / Changes - `skills/guizang-ppt/`:SKILL.md(DesireCore frontmatter 覆盖层 + 上游正文)、`references/`(10)、`assets/`(2 模板 + motion.min.js + 9 张 webp)、`scripts/validate-swiss-deck.mjs`、`LICENSE`(AGPL-3.0)、`NOTICE.md`(署名与合规) - `skills/guizang-ppt/_desirecore/`:DesireCore 维护态(不随上游覆盖) - `frontmatter.yaml`:市场 frontmatter 覆盖层(i18n: zh-CN 源 + en-US 显示串,body 暂回退中文,留给 CI 翻译) - `upstream.json`:上游溯源(commit `014c572`、AGPL-3.0、作者 歸藏/op7418) - `scripts/vendor/guizang-ppt.mjs`:可复用的 vendor 更新脚本(`--src <本地路径>` 或 `--ref <tag>`) - `manifest.json`:`version` → `1.2.3`,`stats.totalSkills` → 25 ## 定位 / Positioning - **仅市场可选安装**:未加入 `builtin-skills.json`,不随客户端开机自动安装;用户在市场按需安装。 - 分类 `creative`;与已有 `pptx`(生成 .pptx 文件)区分:本技能生成 **HTML deck**。 ## 许可与署名 / License & Attribution 上游为 **AGPL-3.0**。本技能保留原 `LICENSE` 与作者署名(`NOTICE.md` + `metadata.author` + `market.maintainer`),作为聚合内容分发。 ## 校验 / Validation - `python3 scripts/i18n/validate-i18n.py skills/guizang-ppt` → `OK: no i18n issues found.` ## 手动更新流程 / Manual update 上游发版时:`node scripts/vendor/guizang-ppt.mjs --ref <tag>` → 核对 diff → 必要时 bump `_desirecore/frontmatter.yaml#version` 与 `manifest.json` → 提交。 --- 🤖 Generated with [Claude Code](https://claude.com/claude-code)
2420 lines
99 KiB
HTML
2420 lines
99 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>[必填] 替换为 PPT 标题 · Deck Title</title>
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@200;300;400;500;600;700;800;900&family=JetBrains+Mono:wght@300;400;500;600&family=Noto+Sans+SC:wght@200;300;400;500;700;900&display=swap" rel="stylesheet">
|
||
<style>
|
||
:root{
|
||
/* ============ 主题色(默认: 🔵 克莱因蓝 IKB) ============
|
||
切换主题: 从 references/themes-swiss.md 复制对应的 :root 块
|
||
整体替换标有"主题色"注释的所有变量,其他散落的 var() 引用无需逐处改 */
|
||
--paper:#fafaf8; /* 主底色: 高级灰白 */
|
||
--paper-rgb:250,250,248;
|
||
--ink:#0a0a0a; /* 文字主色: 近黑 */
|
||
--ink-rgb:10,10,10;
|
||
--grey-1:#f0f0ee; /* 浅灰底 */
|
||
--grey-2:#d4d4d2; /* 中灰(分割线) */
|
||
--grey-3:#737373; /* 暗灰(辅助文字) */
|
||
--accent:#002FA7; /* 高亮色: 克莱因蓝 IKB */
|
||
--accent-rgb:0,47,167;
|
||
--accent-on:#ffffff; /* accent 上的反色文字 */
|
||
--accent-bright:#5B7BFF; /* 暗底高亮: IKB 提亮版 */
|
||
|
||
/* ============ Carbon 文本角色 token (role-based,代替 opacity) ============
|
||
亮底: primary 主文 / secondary 次文 / helper 辅助 / placeholder 占位
|
||
暗底: inverse 自动反向 */
|
||
--text-primary:#0a0a0a; /* = ink, 100% */
|
||
--text-secondary:#525252; /* gray 70 */
|
||
--text-helper:#737373; /* gray 60 */
|
||
--text-placeholder:#a3a3a3; /* gray 40 */
|
||
--text-on-color:#ffffff; /* accent/dark 反色 */
|
||
--border-subtle:#e0e0e0; /* gray 20, 极细分隔 */
|
||
--border-strong:#a3a3a3; /* gray 40, 强分隔 */
|
||
|
||
/* ============ 字体(跨主题固定) ============ */
|
||
--sans:"Inter","Helvetica Neue","Helvetica","Arial","Segoe UI Variable","Segoe UI",system-ui,-apple-system,sans-serif;
|
||
--sans-zh:"PingFang SC","Hiragino Sans GB","Source Han Sans SC","Noto Sans SC","Microsoft YaHei UI","Microsoft YaHei","微软雅黑",sans-serif;
|
||
--mono:"JetBrains Mono","IBM Plex Mono","SF Mono","Cascadia Code","Consolas","Courier New",ui-monospace,monospace;
|
||
|
||
/* ============ Carbon 2x Grid 间距模数 (基础 8px) ============
|
||
参考: https://carbondesignsystem.com/elements/2x-grid/overview/
|
||
任何 padding/gap/margin 优先用这套 token,确保 8px 基线对齐 */
|
||
--sp-3:8px; /* 02 token */
|
||
--sp-4:12px; /* 03 */
|
||
--sp-5:16px; /* 04 */
|
||
--sp-6:24px; /* 05 */
|
||
--sp-7:32px; /* 06 */
|
||
--sp-8:40px; /* 07 */
|
||
--sp-9:48px; /* 08 */
|
||
--sp-10:64px; /* 09 */
|
||
--sp-11:80px; /* 10 */
|
||
--sp-12:96px; /* 11 */
|
||
--sp-13:160px; /* 12 */
|
||
|
||
/* ============ Carbon Motion tokens ============
|
||
https://carbondesignsystem.com/guidelines/motion/overview/
|
||
两套体系:productive (功能) 短 + 锐 / expressive (叙事) 长 + 软 */
|
||
--ease-prod:cubic-bezier(.2,0,.38,.9); /* productive standard */
|
||
--ease-exp:cubic-bezier(.4,.14,.3,1); /* expressive standard */
|
||
--ease-entry-prod:cubic-bezier(0,0,.38,.9); /* productive entrance */
|
||
--ease-entry-exp:cubic-bezier(0,0,.3,1); /* expressive entrance */
|
||
--dur-fast-1:.07s; /* 70ms */
|
||
--dur-fast-2:.11s; /* 110ms */
|
||
--dur-mod-1:.15s; /* 150ms */
|
||
--dur-mod-2:.24s; /* 240ms */
|
||
--dur-slow-1:.4s; /* 400ms */
|
||
--dur-slow-2:.7s; /* 700ms */
|
||
|
||
/* 底部分页组件安全区: 主内容最低处不要进入这条区域 */
|
||
--nav-safe-bottom:8vh;
|
||
}
|
||
*{box-sizing:border-box;margin:0;padding:0}
|
||
html,body{
|
||
width:100%;height:100%;overflow:hidden;
|
||
background:var(--paper);color:var(--ink);
|
||
font-family:var(--sans),var(--sans-zh);
|
||
font-feature-settings:"ss01","cv11";
|
||
-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility
|
||
}
|
||
|
||
/* ============ WebGL 网格背景 ============ */
|
||
canvas.bg{position:fixed;inset:0;width:100vw;height:100vh;z-index:0;display:block;opacity:.55;mix-blend-mode:multiply;pointer-events:none}
|
||
body.dark-bg canvas.bg{mix-blend-mode:screen;opacity:.42}
|
||
body.low-power canvas.bg,
|
||
body.low-power canvas.ascii-bg{display:none!important}
|
||
|
||
/* ============ Deck 容器 + 翻页 ============ */
|
||
#deck{position:fixed;inset:0;width:10000vw;height:100vh;display:flex;flex-wrap:nowrap;transition:transform .9s cubic-bezier(.77,0,.175,1);z-index:10;will-change:transform}
|
||
.slide{
|
||
width:100vw;height:100vh;flex:0 0 100vw;
|
||
position:relative;
|
||
padding:5.5vh 5vw 7vh 5vw;
|
||
display:flex;flex-direction:column;
|
||
overflow:hidden;
|
||
background:var(--paper);color:var(--ink);
|
||
}
|
||
.slide.grey{background:var(--grey-1)}
|
||
.slide.dark{background:var(--ink);color:var(--paper)}
|
||
.slide.dark .grey-only{display:none}
|
||
.slide.accent{background:var(--accent);color:var(--accent-on)}
|
||
.slide.accent .accent-block{background:var(--accent-on);color:var(--accent)}
|
||
|
||
/* Hero 页透出网格背景多一点 */
|
||
.slide.hero{background:transparent}
|
||
.slide.hero.grey{background:rgba(var(--paper-rgb),.86);backdrop-filter:blur(1px)}
|
||
.slide.hero.dark{background:rgba(var(--ink-rgb),.92);color:var(--paper)}
|
||
|
||
/* ============ 装饰: 极细分隔线 + 点阵矩阵 ============ */
|
||
.rule{width:100%;height:1px;background:currentColor;opacity:.18;margin:0}
|
||
.rule.thick{height:2px;opacity:.85}
|
||
.rule.accent{background:var(--accent);opacity:1;height:2px}
|
||
.rule.v{width:1px;height:100%;margin:0}
|
||
|
||
/* 点阵矩阵 (dot matrix) - 用 radial-gradient */
|
||
.dots{
|
||
background-image:radial-gradient(currentColor 1px, transparent 1px);
|
||
background-size:12px 12px;
|
||
background-position:0 0;
|
||
opacity:.18;
|
||
}
|
||
.dots-fine{
|
||
background-image:radial-gradient(currentColor 0.8px, transparent 0.8px);
|
||
background-size:8px 8px;
|
||
opacity:.14;
|
||
}
|
||
.dots-bold{
|
||
background-image:radial-gradient(currentColor 1.4px, transparent 1.4px);
|
||
background-size:18px 18px;
|
||
opacity:.22;
|
||
}
|
||
|
||
/* ============ Chrome (顶部 meta) + Foot (底部) ============ */
|
||
.chrome{
|
||
display:flex;justify-content:space-between;align-items:flex-start;
|
||
font-family:var(--mono);
|
||
font-size:14px;letter-spacing:.16em;text-transform:uppercase;
|
||
opacity:.7;margin-bottom:auto
|
||
}
|
||
.chrome .l,.chrome .r{display:flex;gap:1.6em;align-items:center}
|
||
.chrome .sep{width:24px;height:1px;background:currentColor;opacity:.5}
|
||
|
||
.foot{
|
||
margin-top:auto;
|
||
display:flex;justify-content:space-between;align-items:flex-end;
|
||
font-family:var(--mono);
|
||
font-size:14px;letter-spacing:.14em;text-transform:uppercase;
|
||
opacity:.55;
|
||
padding-top:2vh;
|
||
border-top:1px solid currentColor;
|
||
border-color:rgba(127,127,127,.25)
|
||
}
|
||
.foot .nb{font-family:var(--sans);font-weight:600;letter-spacing:.04em}
|
||
|
||
/* ============ Tag / Kicker / Meta 标签 ============ */
|
||
.kicker{
|
||
font-family:var(--mono);
|
||
font-size:14px;letter-spacing:.24em;text-transform:uppercase;
|
||
opacity:.65;margin-bottom:2.4vh;
|
||
display:inline-flex;align-items:center;gap:.8em
|
||
}
|
||
.kicker::before{
|
||
content:"";width:24px;height:1px;background:currentColor;opacity:.6
|
||
}
|
||
.kicker.no-line::before{display:none}
|
||
.kicker.accent{color:var(--accent);opacity:1;font-weight:600}
|
||
|
||
.tag{
|
||
display:inline-block;
|
||
font-family:var(--mono);
|
||
font-size:14px;letter-spacing:.18em;text-transform:uppercase;
|
||
padding:5px 10px;border:1px solid currentColor;opacity:.85
|
||
}
|
||
.tag.solid{background:currentColor;color:var(--paper);border-color:transparent}
|
||
.tag.accent{background:var(--accent);color:var(--accent-on);border-color:transparent;opacity:1}
|
||
|
||
/* ============ 标题层级 (无衬线 · 极轻字重 · 极致字号对比) ============ */
|
||
.h-hero{
|
||
font-family:var(--sans),var(--sans-zh);
|
||
font-weight:200;
|
||
font-size:11vw;
|
||
line-height:.92;
|
||
letter-spacing:-.04em;
|
||
}
|
||
.h-hero-zh{
|
||
font-family:var(--sans),var(--sans-zh);
|
||
font-weight:200;
|
||
font-size:8.4vw;
|
||
line-height:.96;
|
||
letter-spacing:-.025em;
|
||
}
|
||
.h-xl{
|
||
font-family:var(--sans),var(--sans-zh);
|
||
font-weight:200;
|
||
font-size:6vw;
|
||
line-height:1;
|
||
letter-spacing:-.03em;
|
||
}
|
||
.h-xl-zh{
|
||
font-family:var(--sans),var(--sans-zh);
|
||
font-weight:200;
|
||
font-size:5vw;
|
||
line-height:1.05;
|
||
letter-spacing:-.025em;
|
||
}
|
||
.h-md{
|
||
font-family:var(--sans),var(--sans-zh);
|
||
font-weight:300;
|
||
font-size:2.6vw;
|
||
line-height:1.18;
|
||
letter-spacing:-.015em;
|
||
}
|
||
.h-sub{
|
||
font-family:var(--sans),var(--sans-zh);
|
||
font-weight:400;
|
||
font-size:2.2vw;
|
||
line-height:1.3;
|
||
letter-spacing:-.01em;
|
||
opacity:.7;
|
||
}
|
||
|
||
/* ============ 正文 / 引语 / 元数据 ============ */
|
||
.lead{
|
||
font-family:var(--sans),var(--sans-zh);
|
||
font-weight:400;
|
||
font-size:1.55vw;
|
||
line-height:1.4;
|
||
letter-spacing:-.005em;
|
||
opacity:.86;
|
||
}
|
||
.body{
|
||
font-family:var(--sans),var(--sans-zh);
|
||
font-weight:400;
|
||
font-size:max(18px,1.08vw);
|
||
line-height:1.6;
|
||
letter-spacing:0;
|
||
opacity:.78;
|
||
}
|
||
.body-sm{
|
||
font-family:var(--sans),var(--sans-zh);
|
||
font-weight:400;
|
||
font-size:max(16px,.92vw);
|
||
line-height:1.55;
|
||
opacity:.7;
|
||
}
|
||
.meta{
|
||
font-family:var(--mono);
|
||
font-size:max(14px,.82vw);
|
||
letter-spacing:.18em;text-transform:uppercase;
|
||
opacity:.6;
|
||
}
|
||
.meta-row{
|
||
display:flex;gap:1.4em;align-items:baseline;flex-wrap:wrap;
|
||
font-family:var(--mono);
|
||
font-size:max(14px,.88vw);letter-spacing:.16em;text-transform:uppercase;
|
||
opacity:.65;
|
||
}
|
||
.meta-row span:not(.dot){display:inline-block}
|
||
.meta-row .dot{
|
||
display:inline-block;width:4px;height:4px;border-radius:50%;
|
||
background:currentColor;opacity:.5;vertical-align:middle
|
||
}
|
||
|
||
/* ============ KPI Hero (视觉英雄数据) ============ */
|
||
.kpi-hero{
|
||
font-family:var(--sans);
|
||
font-weight:800;
|
||
font-size:22vw;
|
||
line-height:.82;
|
||
letter-spacing:-.05em;
|
||
font-feature-settings:"tnum","ss01";
|
||
}
|
||
.kpi-hero .unit{
|
||
font-family:var(--sans),var(--sans-zh);
|
||
font-weight:500;
|
||
font-size:.18em;
|
||
letter-spacing:0;
|
||
opacity:.5;
|
||
margin-left:.12em;
|
||
vertical-align:.5em;
|
||
}
|
||
.kpi-hero.accent{color:var(--accent)}
|
||
.kpi-big{
|
||
font-family:var(--sans);
|
||
font-weight:800;
|
||
font-size:11vw;
|
||
line-height:.85;
|
||
letter-spacing:-.04em;
|
||
font-feature-settings:"tnum"
|
||
}
|
||
.kpi-mid{
|
||
font-family:var(--sans);
|
||
font-weight:700;
|
||
font-size:6vw;
|
||
line-height:.88;
|
||
letter-spacing:-.03em;
|
||
font-feature-settings:"tnum"
|
||
}
|
||
|
||
/* ============ Stat Card (数据卡片 · 极简) ============ */
|
||
.stat-card{
|
||
display:flex;flex-direction:column;
|
||
gap:.6vh;align-items:flex-start;
|
||
padding-top:1.6vh;
|
||
border-top:2px solid currentColor;
|
||
}
|
||
.stat-card.thin{border-top-width:1px;border-color:rgba(127,127,127,.4)}
|
||
.stat-card.accent-top{border-top-color:var(--accent);border-top-width:3px}
|
||
.stat-card .stat-label{
|
||
font-family:var(--mono);
|
||
font-size:max(14px,.82vw);
|
||
letter-spacing:.24em;text-transform:uppercase;
|
||
opacity:.6;
|
||
}
|
||
.stat-card .stat-nb{
|
||
font-family:var(--sans);
|
||
font-weight:800;
|
||
font-size:5.6vw;
|
||
line-height:.88;
|
||
letter-spacing:-.035em;
|
||
font-feature-settings:"tnum";
|
||
margin-top:.4vh;
|
||
}
|
||
.stat-card .stat-nb .stat-unit{
|
||
font-family:var(--sans),var(--sans-zh);
|
||
font-weight:500;
|
||
font-size:.32em;
|
||
letter-spacing:0;
|
||
opacity:.6;
|
||
margin-left:.14em;
|
||
vertical-align:.4em;
|
||
}
|
||
.stat-card .stat-note{
|
||
font-family:var(--sans),var(--sans-zh);
|
||
font-weight:400;
|
||
font-size:max(16px,.98vw);
|
||
line-height:1.5;
|
||
opacity:.7;
|
||
margin-top:.6vh;
|
||
}
|
||
.grid-4 .stat-card .stat-nb{font-size:4.6vw}
|
||
.grid-3 .stat-card .stat-nb{font-size:6.4vw}
|
||
.grid-6 .stat-card .stat-nb{font-size:4vw}
|
||
|
||
/* ============ Accent Block (高亮色块包裹内容) ============ */
|
||
.accent-block{
|
||
background:var(--accent);color:var(--accent-on);
|
||
padding:2.4vh 2vw;
|
||
}
|
||
.accent-block.tight{padding:1.4vh 1.4vw}
|
||
.accent-block .h-md,.accent-block .h-xl,.accent-block .kpi-mid{color:var(--accent-on)}
|
||
|
||
.ink-block{
|
||
background:var(--ink);color:var(--paper);
|
||
padding:2.4vh 2vw
|
||
}
|
||
.grey-block{
|
||
background:var(--grey-1);
|
||
padding:2.4vh 2vw
|
||
}
|
||
|
||
/* 高亮文字 (mark 风格) */
|
||
.mark{
|
||
background:var(--accent);color:var(--accent-on);
|
||
padding:0 .2em;
|
||
box-decoration-break:clone;
|
||
-webkit-box-decoration-break:clone;
|
||
}
|
||
.mark.ink{background:var(--ink);color:var(--paper)}
|
||
.underline-accent{
|
||
background-image:linear-gradient(to bottom, transparent 70%, var(--accent) 70%, var(--accent) 96%, transparent 96%);
|
||
padding:0 .05em
|
||
}
|
||
|
||
/* ============ Pipeline / Step ============ */
|
||
.pipeline-section{margin-top:3.2vh;padding-top:2.2vh;border-top:1px solid rgba(127,127,127,.3)}
|
||
.pipeline-section:first-of-type{border-top:0;padding-top:0;margin-top:2.4vh}
|
||
.pipeline-label{
|
||
font-family:var(--mono);
|
||
font-size:max(14px,.84vw);
|
||
letter-spacing:.24em;text-transform:uppercase;
|
||
opacity:.6;margin-bottom:1.8vh;
|
||
}
|
||
.pipeline{
|
||
display:grid;
|
||
grid-template-columns:repeat(5,1fr);
|
||
gap:1vw;
|
||
}
|
||
.pipeline[data-cols="3"]{grid-template-columns:repeat(3,1fr)}
|
||
.pipeline[data-cols="4"]{grid-template-columns:repeat(4,1fr)}
|
||
.pipeline[data-cols="6"]{grid-template-columns:repeat(6,1fr)}
|
||
.step{
|
||
display:flex;flex-direction:column;gap:.6vh;
|
||
padding-top:1.2vh;
|
||
border-top:2px solid currentColor;
|
||
}
|
||
.step.accent-top{border-top-color:var(--accent);border-top-width:3px}
|
||
.step-nb{
|
||
font-family:var(--mono);
|
||
font-weight:500;
|
||
font-size:max(14px,1vw);
|
||
opacity:.5;letter-spacing:.04em
|
||
}
|
||
.step-title{
|
||
font-family:var(--sans),var(--sans-zh);
|
||
font-weight:700;
|
||
font-size:1.4vw;
|
||
letter-spacing:-.01em;
|
||
line-height:1.2;
|
||
}
|
||
.step-desc{
|
||
font-family:var(--sans),var(--sans-zh);
|
||
font-weight:400;
|
||
font-size:max(16px,.94vw);
|
||
line-height:1.45;
|
||
opacity:.7;
|
||
}
|
||
|
||
/* ============ 网格系统 (模块化网格) ============ */
|
||
.frame{flex:1;display:flex;flex-direction:column;min-height:0;overflow:hidden}
|
||
.frame.grid-2-7-5,
|
||
.frame.grid-2-6-6,
|
||
.frame.grid-2-8-4,
|
||
.frame.grid-2-4-8,
|
||
.frame.grid-3-3,
|
||
.frame.grid-12,
|
||
.frame.grid-6,
|
||
.frame.grid-4,
|
||
.frame.grid-3{display:grid}
|
||
|
||
/* 12 列模块化网格 (瑞士风核心) */
|
||
.grid-12{
|
||
display:grid;
|
||
grid-template-columns:repeat(12,1fr);
|
||
gap:2vh 1.2vw;
|
||
align-items:start;
|
||
}
|
||
.span-2{grid-column:span 2}
|
||
.span-3{grid-column:span 3}
|
||
.span-4{grid-column:span 4}
|
||
.span-5{grid-column:span 5}
|
||
.span-6{grid-column:span 6}
|
||
.span-7{grid-column:span 7}
|
||
.span-8{grid-column:span 8}
|
||
.span-9{grid-column:span 9}
|
||
.span-12{grid-column:span 12}
|
||
|
||
/* 经典分栏 */
|
||
.grid-2-7-5{display:grid;grid-template-columns:7fr 5fr;gap:3vw 4vh;align-items:start}
|
||
.grid-2-6-6{display:grid;grid-template-columns:1fr 1fr;gap:3vw 4vh;align-items:start}
|
||
.grid-2-8-4{display:grid;grid-template-columns:8fr 4fr;gap:3vw 4vh;align-items:start}
|
||
.grid-2-4-8{display:grid;grid-template-columns:4fr 8fr;gap:3vw 4vh;align-items:start}
|
||
.grid-3{display:grid;grid-template-columns:repeat(3,1fr);gap:3vw 4vh;align-items:start}
|
||
.grid-3-3{display:grid;grid-template-columns:repeat(3,1fr);grid-auto-rows:minmax(0,1fr);gap:2.4vh 2vw}
|
||
.grid-4{display:grid;grid-template-columns:repeat(2,1fr);grid-template-rows:repeat(2,1fr);gap:3vh 3vw;flex:1;align-content:center}
|
||
.grid-6{display:grid;grid-template-columns:repeat(3,1fr);grid-template-rows:repeat(2,1fr);gap:3vh 2.4vw;flex:1;align-content:center}
|
||
|
||
/* 工具类 */
|
||
.col{display:flex;flex-direction:column;gap:2vh}
|
||
.row{display:flex;align-items:center;gap:2vw}
|
||
.fill{flex:1}
|
||
.center{align-items:center;justify-content:center;text-align:center}
|
||
.right{text-align:right;justify-self:end}
|
||
.top{align-self:start}
|
||
.bottom{align-self:end}
|
||
.va-center{align-self:center}
|
||
.nowrap{white-space:nowrap}
|
||
|
||
/* 非对称定位 */
|
||
.pos-absolute{position:absolute}
|
||
.bottom-left{position:absolute;left:5vw;bottom:8vh;max-width:50vw}
|
||
.bottom-right{position:absolute;right:5vw;bottom:8vh;max-width:50vw;text-align:right}
|
||
.top-right{position:absolute;right:5vw;top:5.5vh;text-align:right}
|
||
.top-left{position:absolute;left:5vw;top:5.5vh}
|
||
|
||
/* ============ Callout (引用框 · 极简) ============ */
|
||
.callout{
|
||
padding:2vh 2vw;
|
||
border-left:3px solid var(--accent);
|
||
font-family:var(--sans),var(--sans-zh);
|
||
font-size:max(14px,1vw);
|
||
line-height:1.55;
|
||
opacity:.9;
|
||
}
|
||
.callout.ink{border-left-color:currentColor}
|
||
.callout .cite,.callout .callout-src{
|
||
display:block;margin-top:1.2vh;
|
||
font-family:var(--mono);
|
||
font-size:14px;letter-spacing:.16em;text-transform:uppercase;
|
||
opacity:.6;
|
||
}
|
||
|
||
/* ============ Icons (Lucide via CDN) ============ */
|
||
.ico{width:1em;height:1em;display:inline-block;vertical-align:-.12em;stroke:currentColor;fill:none;stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round;flex-shrink:0}
|
||
.ico-lg,.ico-md,.ico-sm{fill:none;stroke:currentColor;stroke-linecap:round;stroke-linejoin:round}
|
||
.ico-lg{width:2.4vw;height:2.4vw;stroke-width:1.4;display:inline-block}
|
||
.ico-md{width:1.6vw;height:1.6vw;stroke-width:1.6;display:inline-block;vertical-align:-.4em}
|
||
.ico-sm{width:1vw;height:1vw;stroke-width:1.8;display:inline-block;vertical-align:-.15em;opacity:.7}
|
||
|
||
/* ============ 几何小图标 (装饰) ============ */
|
||
.geo-dot{width:.7vw;height:.7vw;border-radius:50%;background:var(--accent);display:inline-block;vertical-align:middle}
|
||
.geo-square{width:.7vw;height:.7vw;background:var(--accent);display:inline-block;vertical-align:middle}
|
||
.geo-line{width:2vw;height:2px;background:var(--accent);display:inline-block;vertical-align:middle}
|
||
.geo-circle-o{width:.9vw;height:.9vw;border:2px solid currentColor;border-radius:50%;display:inline-block;vertical-align:middle}
|
||
|
||
/* ============ 图片 frame-img ============ */
|
||
.frame-img{overflow:hidden;position:relative;background:var(--paper);box-sizing:border-box;width:100%}
|
||
.slide.dark .frame-img{background:rgba(255,255,255,.06)}
|
||
.frame-img > img{width:100%;height:100%;object-fit:cover;object-position:center center;display:block}
|
||
.frame-img.fit-contain > img{object-fit:contain;object-position:center center}
|
||
.frame-img.pos-top > img{object-position:top center}
|
||
.frame-img.pos-face > img{object-position:center 35%}
|
||
.frame-img.r-16x9{aspect-ratio:16/9;max-height:64vh}
|
||
.frame-img.r-21x9{aspect-ratio:21/9;max-height:54vh}
|
||
.frame-img.r-16x10{aspect-ratio:16/10;max-height:56vh}
|
||
.frame-img.r-4x3{aspect-ratio:4/3;max-height:56vh}
|
||
.frame-img.r-3x2{aspect-ratio:3/2;max-height:46vh}
|
||
.frame-img.r-3x4{aspect-ratio:3/4;max-height:60vh}
|
||
.frame-img.r-1x1{aspect-ratio:1/1;max-height:50vh}
|
||
.frame-img.h-16{height:16vh}
|
||
.frame-img.h-18{height:18vh}
|
||
.frame-img.h-22{height:22vh}
|
||
.frame-img.h-26{height:26vh}
|
||
.frame-img.h-28{height:28vh}
|
||
.frame-img.h-32{height:32vh}
|
||
.frame-img.swiss-lined{border-top:2px solid var(--accent)}
|
||
.frame-img.swiss-keyline{border:0}
|
||
|
||
/* P22 Image Hero: 下半屏内容区必须和图片拉开距离,不要贴在图下沿 */
|
||
.image-hero-body{
|
||
display:grid;grid-template-columns:6fr 6fr;gap:3vw;
|
||
padding:6.6vh 5vw 4.4vh;flex:1;align-content:start
|
||
}
|
||
.image-hero-stats{
|
||
display:grid;grid-template-columns:repeat(3,1fr);gap:2vw;align-items:start
|
||
}
|
||
.img-cap{
|
||
display:block;margin-top:.8vh;
|
||
font-family:var(--mono);
|
||
font-size:max(14px,.82vw);
|
||
letter-spacing:.2em;text-transform:uppercase;
|
||
opacity:.6;
|
||
}
|
||
figure.frame-img,figure.tile{margin:0;display:flex;flex-direction:column;min-width:0}
|
||
figure.tile > .frame-img{flex:0 0 auto}
|
||
|
||
/* 瑞士风图文混排: 只用直角、发丝线、单一 accent;图像容器不加圆角/阴影 */
|
||
.swiss-img-split{display:grid;grid-template-columns:5fr 7fr;gap:3vw;align-items:start;flex:1;min-height:0}
|
||
.swiss-img-split.reverse{grid-template-columns:7fr 5fr}
|
||
.swiss-img-split.align-bottom{align-items:end}
|
||
.swiss-img-split.align-bottom .swiss-img-copy{align-self:end}
|
||
.swiss-img-split.align-image-bottom{align-items:end;padding-bottom:var(--nav-safe-bottom)}
|
||
.swiss-img-split.align-image-bottom .swiss-img-copy{align-self:end}
|
||
.swiss-img-split.align-image-bottom figure.tile{align-self:end;position:relative}
|
||
.swiss-img-split.align-image-bottom figure.tile > .swiss-img-caption{
|
||
position:absolute;left:0;right:0;top:calc(100% + var(--sp-4));margin-top:0
|
||
}
|
||
.nav-safe-bottom{padding-bottom:var(--nav-safe-bottom)}
|
||
.nav-safe-bottom-tight{padding-bottom:calc(var(--nav-safe-bottom) * .65)}
|
||
.swiss-img-copy{display:flex;flex-direction:column;gap:var(--sp-6);min-width:0}
|
||
.swiss-img-copy .rule{margin:0}
|
||
.swiss-img-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--sp-5);align-items:start;margin-top:var(--sp-7)}
|
||
.swiss-img-grid.two{grid-template-columns:repeat(2,1fr)}
|
||
.swiss-img-grid.tight{margin-top:0}
|
||
.swiss-img-caption{
|
||
display:flex;justify-content:space-between;gap:var(--sp-5);
|
||
margin-top:var(--sp-4);
|
||
font-family:var(--mono);font-size:max(14px,.74vw);
|
||
letter-spacing:.16em;text-transform:uppercase;color:var(--text-helper);
|
||
border-top:1px solid var(--border-subtle);padding-top:var(--sp-4)
|
||
}
|
||
.swiss-img-caption strong{
|
||
font-family:var(--sans),var(--sans-zh);font-size:max(16px,.9vw);
|
||
font-weight:600;letter-spacing:0;text-transform:none;color:var(--text-primary)
|
||
}
|
||
|
||
/* ============ Bar Chart (扁平几何图表) ============ */
|
||
.bar-chart{display:flex;flex-direction:column;gap:1.4vh}
|
||
.bar-row{display:grid;grid-template-columns:8em 1fr 4em;gap:1.4vw;align-items:center}
|
||
.bar-row .bar-label{font-family:var(--mono);font-size:max(14px,.84vw);letter-spacing:.14em;text-transform:uppercase;opacity:.7}
|
||
.bar-row .bar-track{height:14px;background:rgba(127,127,127,.18);position:relative}
|
||
.bar-row .bar-fill{height:100%;background:var(--accent);position:absolute;left:0;top:0}
|
||
.bar-row .bar-fill.ink{background:currentColor}
|
||
.bar-row .bar-value{font-family:var(--sans);font-weight:700;font-size:max(16px,1.05vw);text-align:right;font-feature-settings:"tnum"}
|
||
|
||
/* ============ 导航 ============ */
|
||
/* 底部分页导航: 仅保留方块本身,无背景无描边 */
|
||
#nav{position:fixed;left:50%;bottom:2vh;transform:translateX(-50%);z-index:30;display:flex;gap:10px;padding:0;background:transparent;border:0}
|
||
#nav .dot{width:6px;height:6px;background:rgba(0,0,0,.28);cursor:pointer;transition:all .25s ease;border:0;padding:0;border-radius:0}
|
||
#nav .dot:hover{background:rgba(0,0,0,.55)}
|
||
#nav .dot.active{background:var(--accent);width:18px}
|
||
body.dark-bg #nav .dot{background:rgba(255,255,255,.32)}
|
||
body.dark-bg #nav .dot.active{background:var(--accent)}
|
||
|
||
#hint{position:fixed;bottom:2.4vh;right:2.5vw;z-index:30;font-family:var(--mono);font-size:14px;letter-spacing:.14em;text-transform:uppercase;opacity:.4;color:var(--ink-tint, currentColor)}
|
||
body.dark-bg #hint{color:var(--paper);opacity:.4}
|
||
body.low-power #hint{color:var(--accent);opacity:.72}
|
||
body.dark-bg.low-power #hint{color:var(--paper);opacity:.72}
|
||
|
||
/* ESC 索引页: 动画元素强制可见 (覆盖 motion-ready 的 opacity:0) */
|
||
#overview [data-anim]{opacity:1!important;transform:none!important}
|
||
#overview .slide *{animation:none!important;transition:none!important}
|
||
|
||
/* 统一卡片样式 token: card-fill (默认灰底 · 中性) + card-ink (反转高对比) + card-accent (单一焦点) */
|
||
.card-fill{background:#f5f5f4;border:0;color:var(--text-primary)}
|
||
.card-ink{background:var(--ink);border:0;color:var(--paper)}
|
||
.card-ink .t-meta,.card-ink .t-cat{color:rgba(255,255,255,.6)}
|
||
.card-accent{background:var(--accent);border:0;color:var(--accent-on)}
|
||
.card-accent .t-meta,.card-accent .t-cat{color:var(--accent-on)}
|
||
|
||
/* ============ 动效 (与原模板一致) ============ */
|
||
[data-anim]{opacity:1}
|
||
body.motion-ready [data-anim]{opacity:0}
|
||
body.motion-ready [data-anim="left"]{transform:translateX(-24px)}
|
||
body.motion-ready [data-anim="right"]{transform:translateX(24px)}
|
||
body.motion-ready [data-anim="line"]{opacity:0;transform:translateY(10px)}
|
||
body.motion-ready [data-animate="pipeline"] [data-anim]{opacity:.15}
|
||
body.low-power #deck{transition:none!important}
|
||
body.low-power *,
|
||
body.low-power *::before,
|
||
body.low-power *::after{animation:none!important;transition:none!important}
|
||
body.low-power.motion-ready [data-anim],
|
||
body.low-power [data-anim]{opacity:1!important;transform:none!important}
|
||
|
||
/* ============================================================
|
||
↓↓↓ V2 EXTENSIONS · Canvas Mode + 参考图新类
|
||
验证效果后沉淀回 template-swiss.html
|
||
============================================================ */
|
||
|
||
/* Windows 适配:雅黑没有 ExtraLight 200,中文大字号字重补偿 */
|
||
body.is-win .name-mega,
|
||
body.is-win .num-mega,
|
||
body.is-win .kpi-thin,
|
||
body.is-win .tl-node .multi{
|
||
font-weight:300;letter-spacing:-.02em;
|
||
}
|
||
body.is-win [style*="font-weight:200"]{font-weight:300 !important}
|
||
|
||
/* 全屏铺满模式 · 关闭 WebGL · 卡片即页面 */
|
||
body.canvas-mode{background:var(--paper)}
|
||
body.canvas-mode canvas.bg{display:none !important}
|
||
body.canvas-mode .slide{background:var(--paper);padding:0;align-items:stretch;justify-content:stretch}
|
||
body.canvas-mode .slide.hero{background:var(--paper)}
|
||
|
||
.canvas-card{
|
||
width:100vw;
|
||
height:100vh;
|
||
background:var(--paper);color:var(--ink);
|
||
padding:5.6vh 5vw 4.4vh;
|
||
display:flex;flex-direction:column;
|
||
position:relative;overflow:hidden;
|
||
box-shadow:none;
|
||
border-radius:0;
|
||
}
|
||
.slide.dark .canvas-card{background:var(--ink);color:var(--paper)}
|
||
.slide.accent .canvas-card{background:var(--accent);color:var(--accent-on)}
|
||
.slide.grey .canvas-card{background:var(--grey-1);color:var(--ink)}
|
||
.slide.split .canvas-card{padding:0;flex-direction:row}
|
||
|
||
/* ============ ASCII 点阵呼吸场 · IKB 封面/封底专用 ============
|
||
用法:在 .canvas-card(或 split .half.b-accent)内首位插入 <canvas class="ascii-bg" aria-hidden="true">.
|
||
动画由本文件底部的 ASCII IIFE 自动启动,所有 canvas.ascii-bg 都会被扫到.
|
||
其他内容靠 .canvas-card > *:not(.ascii-bg){z-index:1} 自动浮在上层. */
|
||
canvas.ascii-bg{
|
||
position:absolute;inset:0;width:100%;height:100%;
|
||
pointer-events:none;z-index:0;
|
||
mix-blend-mode:screen;opacity:.92;
|
||
}
|
||
.canvas-card > *:not(.ascii-bg){position:relative;z-index:1}
|
||
.slide.accent .canvas-card .chrome-min{color:rgba(255,255,255,.62)}
|
||
.slide.accent .canvas-card .t-meta{color:rgba(255,255,255,.7)}
|
||
.split-half > .half.b-accent .chrome-min{color:rgba(255,255,255,.62)}
|
||
|
||
/* 编号目录页 · 超大数字 + 国家名风格 — 越大越细 */
|
||
.num-mega{
|
||
font-family:var(--sans);font-weight:200;
|
||
font-size:9vw;line-height:1;letter-spacing:-.04em;
|
||
font-feature-settings:"tnum"
|
||
}
|
||
.num-mega.thin{font-weight:200}
|
||
.name-mega{
|
||
font-family:var(--sans);font-weight:200;
|
||
font-size:9vw;line-height:1;letter-spacing:-.035em;
|
||
}
|
||
.name-mega.muted{color:var(--grey-3)}
|
||
|
||
/* 细体超大 KPI — 字号越大权重越低 */
|
||
.kpi-thin{
|
||
font-family:var(--sans);font-weight:200;
|
||
font-size:14vw;line-height:.92;letter-spacing:-.045em;
|
||
font-feature-settings:"tnum"
|
||
}
|
||
.kpi-thin .unit{font-size:.3em;font-weight:300;opacity:.55;margin-left:.15em;vertical-align:.6em}
|
||
.kpi-thin.accent{color:var(--accent)}
|
||
.kpi-thin-sm{
|
||
font-family:var(--sans);font-weight:250;
|
||
font-size:5.6vw;line-height:1.04;letter-spacing:-.03em;
|
||
font-feature-settings:"tnum"
|
||
}
|
||
.kpi-thin-sm .unit{font-size:.3em;font-weight:300;opacity:.55;margin-left:.12em;vertical-align:.45em}
|
||
|
||
/* 4 列细线 KPI 行 — 顶部一根 hairline,内部不再加竖线 */
|
||
/* 4 列 KPI: 用纵向分割线建立网格感 (Carbon 2x grid 模数) */
|
||
.kpi-row-4{
|
||
display:grid;grid-template-columns:repeat(4,1fr);
|
||
gap:0;padding-top:2.4vh;
|
||
border-top:1px solid var(--grey-2)
|
||
}
|
||
.kpi-row-4 > .kpi-cell{
|
||
padding:1.6vh 1.6vw 0;
|
||
border-left:1px solid var(--grey-2)
|
||
}
|
||
.kpi-row-4 > .kpi-cell:first-child{padding-left:0;border-left:none}
|
||
.kpi-cell .lbl{font-family:var(--mono);font-size:max(14px,.78vw);letter-spacing:.18em;text-transform:uppercase;opacity:.55;margin-bottom:1.2vh}
|
||
.kpi-cell .nb{font-family:var(--sans);font-weight:250;font-size:3.2vw;line-height:1;letter-spacing:-.025em;font-feature-settings:"tnum"}
|
||
.kpi-cell .nb .unit{font-size:.32em;font-weight:300;opacity:.6;margin-left:.1em;vertical-align:.4em}
|
||
.kpi-cell .note{font-family:var(--sans),var(--sans-zh);font-size:max(16px,.92vw);line-height:1.5;opacity:.7;margin-top:1.2vh}
|
||
|
||
/* 时间线轴 — 通用 axis token, 横纵共享
|
||
axis 列 = 24px 固定宽,dot 直径 8px,绝对居中在 axis 列中线 (12px)
|
||
虚线绝对定位 left:12px,与 dot 中心严格对齐 */
|
||
.timeline-v{
|
||
--tl-axis-w:24px; /* axis 列固定宽度 */
|
||
--tl-dot:8px; /* 圆点直径 */
|
||
position:relative;margin-top:var(--sp-7)
|
||
}
|
||
.timeline-v::before{
|
||
content:"";position:absolute;
|
||
left:calc(var(--tl-axis-w) / 2);
|
||
transform:translateX(-50%);
|
||
top:var(--sp-5);bottom:var(--sp-5);
|
||
width:1px;
|
||
background:repeating-linear-gradient(to bottom,currentColor 0 4px,transparent 4px 8px);
|
||
opacity:.35;pointer-events:none;z-index:0
|
||
}
|
||
.tl-node{
|
||
position:relative;
|
||
display:grid;
|
||
grid-template-columns:var(--tl-axis-w) minmax(0,7em) minmax(0,7.6em) 1fr;
|
||
gap:0 var(--sp-5);
|
||
align-items:center;
|
||
padding:var(--sp-7) 0
|
||
}
|
||
/* 进度点: 纯色实心圆,无背景无描边,精确居中在 axis 列中线 */
|
||
.tl-node .dot{
|
||
width:var(--tl-dot);height:var(--tl-dot);
|
||
border-radius:50%;
|
||
background:currentColor;
|
||
justify-self:center;
|
||
z-index:1
|
||
}
|
||
.tl-node.accent .dot{background:var(--accent)}
|
||
.tl-node .yr{
|
||
font-family:var(--mono);font-weight:500;
|
||
font-size:max(14px,1vw);letter-spacing:.04em
|
||
}
|
||
.tl-node .multi{
|
||
font-family:var(--sans);font-weight:200;
|
||
font-size:clamp(28px,2.8vw,56px);
|
||
line-height:.95;letter-spacing:-.025em;
|
||
white-space:nowrap;
|
||
overflow:hidden;
|
||
min-width:0
|
||
}
|
||
.tl-node .multi .unit{
|
||
font-size:.36em;font-weight:300;opacity:.6;
|
||
margin-left:.2em;
|
||
vertical-align:.42em;
|
||
letter-spacing:0
|
||
}
|
||
.tl-node.accent .multi{color:var(--accent)}
|
||
.tl-node .desc{
|
||
font-family:var(--sans),var(--sans-zh);
|
||
font-size:max(16px,.94vw);line-height:1.55;opacity:.78;
|
||
min-width:0
|
||
}
|
||
|
||
/* 横向时间线 (.timeline-h) — 与 .timeline-v 共享 axis token, 视觉语言一致 */
|
||
.timeline-h{
|
||
--tl-axis-w:8px;
|
||
--tl-dot:8px;
|
||
position:relative;
|
||
flex:1;
|
||
display:flex;align-items:center
|
||
}
|
||
.timeline-h::before{
|
||
content:"";position:absolute;
|
||
top:50%;left:5%;right:5%;height:1px;
|
||
transform:translateY(-50%);
|
||
background:repeating-linear-gradient(to right,currentColor 0 4px,transparent 4px 8px);
|
||
opacity:.35;pointer-events:none;z-index:0
|
||
}
|
||
.timeline-h .tl-row{
|
||
position:relative;width:100%;
|
||
display:grid;grid-template-columns:repeat(5,1fr);
|
||
align-items:center
|
||
}
|
||
.timeline-h .th-node{
|
||
position:relative;display:flex;justify-content:center
|
||
}
|
||
/* 横向 dot: 8px 纯色实心,无描边无阴影 (与 P2 保持一致) */
|
||
.timeline-h .th-node .dot{
|
||
width:var(--tl-dot);height:var(--tl-dot);
|
||
border-radius:50%;
|
||
background:var(--ink);
|
||
z-index:1;position:relative
|
||
}
|
||
.timeline-h .th-node.accent .dot{background:var(--accent)}
|
||
.timeline-h .th-node .label{
|
||
position:absolute;left:50%;transform:translateX(-50%);
|
||
width:13vw;text-align:center;
|
||
display:flex;flex-direction:column;gap:.4vh
|
||
}
|
||
.timeline-h .th-node.up .label{bottom:calc(50% + 22px)}
|
||
.timeline-h .th-node.down .label{top:calc(50% + 22px)}
|
||
.timeline-h .th-node .yr{
|
||
font-family:var(--mono);font-size:max(14px,.78vw);letter-spacing:.05em;
|
||
color:var(--text-helper);font-weight:500
|
||
}
|
||
.timeline-h .th-node.accent .yr{color:var(--accent)}
|
||
.timeline-h .th-node .name{
|
||
font-family:var(--sans);font-size:max(16px,1.05vw);font-weight:400;
|
||
color:var(--text-primary);line-height:1.2;letter-spacing:-.005em
|
||
}
|
||
.timeline-h .th-node.accent .name{color:var(--accent)}
|
||
.timeline-h .th-node .desc{
|
||
font-family:var(--sans),var(--sans-zh);font-size:max(14px,.84vw);
|
||
color:var(--text-secondary);font-weight:400;line-height:1.4
|
||
}
|
||
|
||
/* 几何图示 (参考图 5) — SVG-friendly 容器 */
|
||
.geo-icon{width:5vw;height:5vw;display:block;margin-bottom:2.2vh;color:var(--ink);flex-shrink:0}
|
||
.slide.dark .geo-icon{color:var(--paper)}
|
||
.geo-icon svg{width:100%;height:100%;overflow:visible}
|
||
.geo-icon .stroke{fill:none;stroke:currentColor;stroke-width:1.4}
|
||
.geo-icon .stroke-accent{fill:none;stroke:var(--accent);stroke-width:1.4}
|
||
.geo-icon .fill-accent{fill:var(--accent)}
|
||
|
||
/* 卡片网格 (参考图 1 卡片漂浮) — 在 canvas-card 内部再分卡 */
|
||
.sub-grid-3-2{display:grid;grid-template-columns:repeat(3,1fr);grid-template-rows:repeat(2,1fr);gap:1.4vh 1.4vw;flex:1;align-content:stretch;margin-top:3vh}
|
||
.sub-card{
|
||
background:var(--grey-1);
|
||
padding:2.4vh 1.6vw 2vh;
|
||
display:flex;flex-direction:column;
|
||
position:relative;border-radius:3px;
|
||
min-height:0
|
||
}
|
||
.slide.dark .sub-card{background:rgba(255,255,255,.06)}
|
||
.sub-card.accent{background:var(--accent);color:var(--accent-on)}
|
||
.sub-card.ink{background:var(--ink);color:var(--paper)}
|
||
.sub-card .nb-corner{
|
||
position:absolute;top:1.6vh;right:1.4vw;
|
||
font-family:var(--mono);font-size:max(14px,.8vw);
|
||
letter-spacing:.16em;opacity:.55
|
||
}
|
||
.sub-card .ttl{font-family:var(--sans),var(--sans-zh);font-weight:500;font-size:max(17px,1.5vw);line-height:1.2;letter-spacing:-.015em;margin-bottom:1vh}
|
||
.sub-card .desc{font-family:var(--sans),var(--sans-zh);font-size:max(16px,.94vw);line-height:1.55;opacity:.78;margin-top:auto}
|
||
.sub-card .lucide{width:2.4vw;height:2.4vw;stroke-width:1.4;color:currentColor;margin-bottom:1.6vh;flex-shrink:0}
|
||
.sub-card.accent .lucide{color:var(--accent-on)}
|
||
|
||
/* 三层架构纯色块拼图 (参考图 4 + 6) — 横排等高色块 */
|
||
.stack-row{display:grid;grid-template-columns:repeat(3,1fr);gap:1.6vw;flex:1;margin-top:6vh;align-items:stretch}
|
||
.stack-block{
|
||
display:flex;flex-direction:column;
|
||
padding:3.2vh 1.8vw 2.4vh;
|
||
position:relative;
|
||
min-height:0
|
||
}
|
||
.stack-block.b-accent{background:var(--accent);color:var(--accent-on)}
|
||
.stack-block.b-grey{background:var(--grey-1);color:var(--ink)}
|
||
.stack-block.b-ink{background:var(--ink);color:var(--paper)}
|
||
.stack-block .layer-nb{font-family:var(--mono);font-size:max(14px,.84vw);letter-spacing:.18em;opacity:.65;margin-bottom:auto}
|
||
.stack-block .layer-icon{margin-bottom:1.6vh}
|
||
.stack-block .layer-icon svg{width:3vw;height:3vw}
|
||
.stack-block .layer-icon .stroke{fill:none;stroke:currentColor;stroke-width:1.6}
|
||
.stack-block .layer-ttl{font-family:var(--sans),var(--sans-zh);font-weight:400;font-size:max(17px,2vw);line-height:1.1;margin-top:1vh;letter-spacing:-.02em}
|
||
.stack-block .layer-desc{font-family:var(--sans),var(--sans-zh);font-weight:400;font-size:max(16px,.94vw);line-height:1.55;opacity:.88;margin-top:1.4vh}
|
||
.stack-block .lucide{width:2.6vw;height:2.6vw;stroke-width:1.4;margin-bottom:1.6vh;flex-shrink:0}
|
||
.stack-block .layer-tag{font-family:var(--mono);font-size:max(14px,.74vw);letter-spacing:.16em;text-transform:uppercase;opacity:.7;margin-top:1.6vh;border-top:1px solid currentColor;padding-top:1vh}
|
||
|
||
/* 不等高柱状 KPI 塔 (参考图 6) */
|
||
.bar-towers{
|
||
display:grid;grid-template-columns:repeat(4,1fr);
|
||
gap:1.2vw;flex:1;align-items:end;margin-top:auto
|
||
}
|
||
.bar-tower{
|
||
display:flex;flex-direction:column;justify-content:flex-end;
|
||
min-height:0;height:100%
|
||
}
|
||
.bar-tower .cap{
|
||
background:var(--grey-1);
|
||
height:5.6vh;
|
||
display:flex;align-items:center;justify-content:center;
|
||
margin-bottom:.4vh
|
||
}
|
||
.bar-tower .cap svg{width:1.6vw;height:1.6vw;stroke:currentColor;fill:none;stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round}
|
||
.bar-tower .body-block{
|
||
flex:0 1 auto;
|
||
padding:2vh 1.2vw 2vh;
|
||
display:flex;flex-direction:column;justify-content:flex-end;
|
||
min-height:18vh
|
||
}
|
||
.bar-tower .body-block.h-1{min-height:22vh}
|
||
.bar-tower .body-block.h-2{min-height:30vh}
|
||
.bar-tower .body-block.h-3{min-height:38vh}
|
||
.bar-tower .body-block.h-4{min-height:46vh}
|
||
/* 默认所有 KPI 塔统一为浅描边卡 — 不抢戏;只有 .b-accent 突出为 IKB */
|
||
.bar-tower .body-block{background:var(--paper);color:var(--ink);border:1px solid var(--grey-2)}
|
||
.bar-tower .body-block.b-accent{background:var(--accent);color:var(--accent-on);border-color:var(--accent)}
|
||
.bar-tower .lbl{font-family:var(--mono);font-size:max(14px,.82vw);letter-spacing:.16em;text-transform:uppercase;opacity:.65;margin-bottom:1vh}
|
||
.bar-tower .nb{font-family:var(--sans);font-weight:250;font-size:max(20px,2.8vw);line-height:1;letter-spacing:-.03em;font-feature-settings:"tnum"}
|
||
.bar-tower .body-block.b-accent .nb{font-weight:300}
|
||
.bar-tower .nb .unit{font-size:.36em;font-weight:300;opacity:.7;margin-left:.08em;vertical-align:.4em}
|
||
.bar-tower .sub{font-family:var(--sans),var(--sans-zh);font-size:max(16px,.92vw);opacity:.75;margin-top:1.2vh;line-height:1.5}
|
||
.bar-tower .cap{background:var(--grey-1);color:var(--ink)}
|
||
.bar-tower .lucide{width:1.6vw;height:1.6vw;stroke-width:1.4}
|
||
|
||
/* ============ Carbon Productive Type Tokens ============
|
||
用于 PPT 中的"productive 时刻": 列表、表格行、说明文、章节标签
|
||
固定 px 字号,密集紧凑;与 vw-based 的 Expressive 巨字形成双极对比 */
|
||
|
||
/* category label / eyebrow / section tag — small, bold, uppercase */
|
||
.t-cat{
|
||
font-family:var(--mono);
|
||
font-size:14px;font-weight:600;
|
||
letter-spacing:.15em;text-transform:uppercase;
|
||
color:var(--text-helper);line-height:1.3
|
||
}
|
||
.t-cat.accent{color:var(--accent)}
|
||
.t-cat.on-dark{color:rgba(255,255,255,.78)}
|
||
|
||
/* page chrome / breadcrumb / running header — extra-small mono */
|
||
.t-meta{
|
||
font-family:var(--mono);
|
||
font-size:14px;font-weight:500;
|
||
letter-spacing:.14em;text-transform:uppercase;
|
||
color:var(--text-helper);line-height:1.45
|
||
}
|
||
|
||
/* helper / caption — secondary text */
|
||
.t-helper{
|
||
font-family:var(--sans),var(--sans-zh);
|
||
font-size:14px;font-weight:400;
|
||
color:var(--text-helper);line-height:1.5;
|
||
letter-spacing:.005em
|
||
}
|
||
|
||
/* body small — list items, table rows, captions */
|
||
.t-body-sm{
|
||
font-family:var(--sans),var(--sans-zh);
|
||
font-size:16px;font-weight:400;
|
||
color:var(--text-secondary);line-height:1.55;
|
||
letter-spacing:0
|
||
}
|
||
|
||
/* body — paragraphs, descriptions */
|
||
.t-body{
|
||
font-family:var(--sans),var(--sans-zh);
|
||
font-size:18px;font-weight:400;
|
||
color:var(--text-primary);line-height:1.5;
|
||
letter-spacing:-.005em
|
||
}
|
||
|
||
/* body emphasis — 强调正文 */
|
||
.t-body-emp{
|
||
font-family:var(--sans),var(--sans-zh);
|
||
font-size:18px;font-weight:600; /* SemiBold per Carbon */
|
||
color:var(--text-primary);line-height:1.5;
|
||
letter-spacing:-.005em
|
||
}
|
||
|
||
/* productive heading — section title within slide */
|
||
.t-h-prod{
|
||
font-family:var(--sans),var(--sans-zh);
|
||
font-size:22px;font-weight:600;
|
||
color:var(--text-primary);line-height:1.4;
|
||
letter-spacing:-.01em
|
||
}
|
||
|
||
/* dark background variants */
|
||
.slide.dark .t-cat,.slide.dark .t-meta,.slide.dark .t-helper{color:rgba(255,255,255,.62)}
|
||
.slide.dark .t-body-sm{color:rgba(255,255,255,.78)}
|
||
.slide.dark .t-body,.slide.dark .t-body-emp,.slide.dark .t-h-prod{color:var(--paper)}
|
||
.split-half .half.b-ink .t-cat,.split-half .half.b-ink .t-meta,.split-half .half.b-ink .t-helper{color:rgba(255,255,255,.62)}
|
||
.split-half .half.b-ink .t-body-sm{color:rgba(255,255,255,.78)}
|
||
.split-half .half.b-ink .t-body,.split-half .half.b-ink .t-body-emp,.split-half .half.b-ink .t-h-prod{color:var(--paper)}
|
||
|
||
/* 斜杠点阵装饰 (参考图 6 左下) */
|
||
.hatch{
|
||
background-image:repeating-linear-gradient(135deg,currentColor 0 1px,transparent 1px 8px);
|
||
opacity:.55
|
||
}
|
||
|
||
/* ========== V3: 高级点阵装饰 — 圆点 / × 号 / 圆圈 ========== */
|
||
/* 实心圆点矩阵 — 用于大面积装饰 */
|
||
.dot-mat{
|
||
--d:14px;
|
||
background-image:radial-gradient(currentColor 1.4px,transparent 1.6px);
|
||
background-size:var(--d) var(--d);
|
||
background-position:0 0;
|
||
opacity:.5
|
||
}
|
||
.dot-mat.lg{--d:22px}
|
||
.dot-mat.xl{--d:34px}
|
||
.dot-mat.dense{--d:9px;opacity:.62}
|
||
|
||
/* 描边圆圈矩阵 — 工业感 */
|
||
.ring-mat{
|
||
--d:18px;
|
||
background-image:
|
||
radial-gradient(circle at 50% 50%,transparent 2px,currentColor 2px,currentColor 2.6px,transparent 2.7px);
|
||
background-size:var(--d) var(--d);
|
||
opacity:.55
|
||
}
|
||
.ring-mat.lg{--d:28px}
|
||
|
||
/* × 号矩阵 — SVG mask 实现真正的 × 网格平铺 */
|
||
.cross-mat{
|
||
--d:22px;
|
||
background-color:currentColor;
|
||
-webkit-mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 22 22'><g stroke='black' stroke-width='1.4' stroke-linecap='round' fill='none'><line x1='8' y1='8' x2='14' y2='14'/><line x1='14' y1='8' x2='8' y2='14'/></g></svg>");
|
||
mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 22 22'><g stroke='black' stroke-width='1.4' stroke-linecap='round' fill='none'><line x1='8' y1='8' x2='14' y2='14'/><line x1='14' y1='8' x2='8' y2='14'/></g></svg>");
|
||
-webkit-mask-size:var(--d) var(--d);
|
||
mask-size:var(--d) var(--d);
|
||
-webkit-mask-repeat:repeat;mask-repeat:repeat;
|
||
opacity:.42
|
||
}
|
||
.cross-mat.lg{--d:32px}
|
||
|
||
/* ========== V3: 几何水平柱状图 ========== */
|
||
/* 横向柱图: 标签列 + 柱体列 + 数值列 — 严格瑞士网格 */
|
||
.h-bar-chart{
|
||
display:grid;
|
||
grid-template-columns:11em minmax(0,1fr) 8em;
|
||
gap:1.6vh 1.6vw;
|
||
align-items:center;
|
||
margin-top:2.4vh;
|
||
font-feature-settings:"tnum"
|
||
}
|
||
.h-bar-chart .row-lbl{
|
||
font-family:var(--sans),var(--sans-zh);
|
||
font-weight:500;
|
||
font-size:max(14px,1vw);
|
||
letter-spacing:-.005em;
|
||
text-align:left
|
||
}
|
||
.h-bar-chart .row-track{
|
||
height:3.2vh;
|
||
background:var(--grey-1);
|
||
position:relative;
|
||
overflow:hidden
|
||
}
|
||
.h-bar-chart .row-fill{
|
||
height:100%;
|
||
background:var(--ink);
|
||
transition:width 1s cubic-bezier(.5,0,.2,1)
|
||
}
|
||
.h-bar-chart .row-fill.accent{background:var(--accent)}
|
||
.h-bar-chart .row-fill.grey{background:var(--grey-3)}
|
||
.h-bar-chart .row-val{
|
||
font-family:var(--sans);font-weight:250;
|
||
font-size:max(16px,1.5vw);
|
||
letter-spacing:-.02em;
|
||
line-height:1
|
||
}
|
||
.h-bar-chart .row-val .unit{
|
||
font-size:.5em;opacity:.55;font-weight:300;
|
||
margin-left:.15em;letter-spacing:.04em
|
||
}
|
||
|
||
/* 垂直柱图: 用于 KPI 对比页(章节 P8) */
|
||
.v-bar-chart{
|
||
display:grid;
|
||
grid-template-columns:repeat(var(--cols,4),1fr);
|
||
align-items:end;
|
||
gap:1.4vw;
|
||
height:50vh;
|
||
margin-top:3vh
|
||
}
|
||
.v-bar-chart .col{display:flex;flex-direction:column;gap:1.4vh;align-items:stretch;height:100%}
|
||
.v-bar-chart .col-bar{
|
||
flex:1 1 auto;
|
||
background:var(--grey-1);
|
||
border-top:2px solid var(--ink);
|
||
position:relative;
|
||
display:flex;align-items:flex-start;justify-content:center;
|
||
padding-top:1vh
|
||
}
|
||
.v-bar-chart .col-bar.accent{background:var(--accent);border-top-color:var(--accent);color:var(--accent-on)}
|
||
.v-bar-chart .col-bar.ink{background:var(--ink);color:var(--paper);border-top-color:var(--ink)}
|
||
.v-bar-chart .col-bar .v{
|
||
font-family:var(--sans);font-weight:250;
|
||
font-size:max(18px,1.6vw);letter-spacing:-.02em
|
||
}
|
||
.v-bar-chart .col-lbl{
|
||
font-family:var(--mono);font-size:max(14px,.78vw);
|
||
letter-spacing:.14em;text-transform:uppercase;opacity:.6;
|
||
text-align:center;flex:0 0 auto
|
||
}
|
||
|
||
/* 对仗对比双栏 (章节 P9) */
|
||
.duo-compare{
|
||
display:grid;grid-template-columns:1fr 1px 1fr;
|
||
gap:0 3.4vw;
|
||
flex:1;align-items:stretch;margin-top:8vh
|
||
}
|
||
.duo-compare .vrule{background:var(--grey-2);width:1px;align-self:stretch}
|
||
.duo-compare .col{display:flex;flex-direction:column;gap:1.6vh;padding:0 .4vw}
|
||
.duo-compare .col-tag{
|
||
font-family:var(--mono);font-size:max(14px,.78vw);
|
||
letter-spacing:.16em;text-transform:uppercase;
|
||
color:var(--grey-3);
|
||
display:flex;align-items:center;gap:.6vw
|
||
}
|
||
.duo-compare .col-tag .num{
|
||
font-weight:600;color:var(--ink);
|
||
border:1px solid var(--ink);
|
||
padding:.2em .6em;font-size:.92em
|
||
}
|
||
.duo-compare .col.accent .col-tag .num{color:var(--accent);border-color:var(--accent)}
|
||
.duo-compare .col-ttl{
|
||
font-family:var(--sans),var(--sans-zh);font-weight:200;
|
||
font-size:3.6vw;line-height:1;letter-spacing:-.03em
|
||
}
|
||
.duo-compare .col.accent .col-ttl{color:var(--accent)}
|
||
.duo-compare .col-desc{
|
||
font-family:var(--sans),var(--sans-zh);
|
||
font-size:max(16px,1.04vw);line-height:1.55;opacity:.78;
|
||
max-width:30vw
|
||
}
|
||
.duo-compare .col-list{
|
||
list-style:none;display:flex;flex-direction:column;gap:1vh;
|
||
margin-top:auto;padding-top:2vh;border-top:1px solid var(--grey-2)
|
||
}
|
||
.duo-compare .col-list li{
|
||
font-family:var(--sans),var(--sans-zh);
|
||
font-size:max(16px,.94vw);line-height:1.5;
|
||
padding-left:1.4em;position:relative
|
||
}
|
||
.duo-compare .col-list li::before{
|
||
content:"";position:absolute;left:0;top:.6em;
|
||
width:.5em;height:1px;background:currentColor;opacity:.55
|
||
}
|
||
.duo-compare .col.accent .col-list li::before{background:var(--accent);opacity:1}
|
||
|
||
/* 半屏 statement (参考图 7) */
|
||
.split-half{display:grid;grid-template-columns:1fr 1fr;gap:0;flex:1;align-items:stretch}
|
||
.split-half > .half{padding:5vh 3.4vw;display:flex;flex-direction:column;min-width:0}
|
||
.split-half > .half.r-border{border-left:1px solid rgba(127,127,127,.22)}
|
||
.split-half > .half.b-grey{background:var(--grey-1)}
|
||
.split-half > .half.b-accent{background:var(--accent);color:var(--accent-on)}
|
||
.split-half > .half.b-ink{background:var(--ink);color:var(--paper)}
|
||
|
||
/* 极简页眉 — t-meta 风格 (Carbon productive label-01) */
|
||
.canvas-card .chrome-min{
|
||
display:flex;justify-content:space-between;align-items:flex-start;
|
||
font-family:var(--mono);font-size:14px;font-weight:500;
|
||
letter-spacing:.14em;text-transform:uppercase;
|
||
color:var(--text-helper);
|
||
flex:0 0 auto;
|
||
margin-bottom:var(--sp-9); /* 48px */
|
||
}
|
||
.canvas-card .chrome-min.tight{margin-bottom:var(--sp-7)} /* 32px */
|
||
.canvas-card .chrome-min .l, .canvas-card .chrome-min .r{
|
||
max-width:48vw;line-height:1.5
|
||
}
|
||
.slide.dark .canvas-card .chrome-min,.split-half .half.b-ink .canvas-card .chrome-min{color:rgba(255,255,255,.62)}
|
||
|
||
/* 响应式降级 */
|
||
@media (max-width:900px){
|
||
.h-hero{font-size:16vw}
|
||
.h-hero-zh{font-size:13vw}
|
||
.h-xl{font-size:9vw}
|
||
.h-xl-zh{font-size:8vw}
|
||
.kpi-hero{font-size:32vw}
|
||
.kpi-big{font-size:16vw}
|
||
.pipeline{grid-template-columns:repeat(2,1fr)}
|
||
.grid-2-7-5,.grid-2-6-6,.grid-2-8-4,.grid-2-4-8{grid-template-columns:1fr}
|
||
.grid-12{grid-template-columns:repeat(6,1fr)}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body class="canvas-mode">
|
||
<script>
|
||
// Windows 平台标记 — 雅黑没有 ExtraLight,需要字重补偿
|
||
if(/Win/i.test(navigator.platform || navigator.userAgentData?.platform || '')){
|
||
document.body.classList.add('is-win');
|
||
}
|
||
(function(){
|
||
const KEY = 'guizang-ppt-low-power';
|
||
const reduced = matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||
const stored = localStorage.getItem(KEY);
|
||
window.__lowPowerMode = stored === '1' || (stored === null && reduced);
|
||
function updateHint(){
|
||
const hint = document.getElementById('hint');
|
||
if(hint) hint.textContent = `← → 翻页 · B ${window.__lowPowerMode ? '动态' : '静态'} · ESC 索引`;
|
||
}
|
||
window.__setLowPowerMode = function(on, opts={}){
|
||
window.__lowPowerMode = !!on;
|
||
document.body.classList.toggle('low-power', window.__lowPowerMode);
|
||
if(opts.persist !== false) localStorage.setItem(KEY, window.__lowPowerMode ? '1' : '0');
|
||
if(window.__lowPowerMode && document.getAnimations){
|
||
document.getAnimations().forEach(a=>a.cancel());
|
||
}
|
||
updateHint();
|
||
dispatchEvent(new CustomEvent('swiss-low-power-change', {detail:{on:window.__lowPowerMode}}));
|
||
if(window.__playSlide) window.__playSlide(window.__currentSlideIndex || 0);
|
||
};
|
||
document.body.classList.toggle('low-power', window.__lowPowerMode);
|
||
addEventListener('DOMContentLoaded', updateHint, {once:true});
|
||
})();
|
||
</script>
|
||
|
||
<canvas id="bg-grid" class="bg"></canvas>
|
||
<div id="hint">← → 翻页 · B 静态 · ESC 索引</div>
|
||
|
||
<div id="deck">
|
||
|
||
<!-- ============================================================
|
||
SLIDES 插入区 · 在此处填充所有 <section class="slide ..."> 页面
|
||
页面骨架参考 references/layouts-swiss.md
|
||
主题色配置参考 references/themes-swiss.md
|
||
============================================================ -->
|
||
<!-- SLIDES_HERE · 在此处粘贴 <section class="slide ..."> 页面块
|
||
- 页面骨架直接从 references/layouts-swiss.md 拷贝
|
||
- data-animate="..." 必须命中下方 RECIPES 字典里的已有 recipe 名之一(P23/P24 可复用 grid-reveal)
|
||
- 主题色配置参考 references/themes-swiss.md (默认 IKB 克莱因蓝) -->
|
||
|
||
<!-- ============ 示例:第 1 页 · Hero Cover · IKB 满屏 + ASCII 呼吸场(默认推荐) ============
|
||
⚠️ P0 对齐法则(每页都要过):
|
||
1. .canvas-card 已自带 padding:5.6vh 5vw 4.4vh,所有页面内容直接放在 canvas-card 子元素里,
|
||
子元素**不要再加水平 padding**,否则会比 chrome-min 内缩一圈、左右不对齐.
|
||
2. .slide.split .canvas-card{padding:0} 已被 CSS 覆盖,
|
||
split 模式下两个 .half 自己控制 padding(常用 5.6vh 3.6vw 4.4vh),与本规则不冲突.
|
||
3. 大字号一律用双约束 font-size:min(Xvw, Yvh),Y ≥ X * 1.6 才不会在 16:9 屏被高度截断.
|
||
4. kicker 必须在大标题"上方",不是左侧——禁止 grid-template-columns:auto 1fr 把它们压成左右.
|
||
|
||
封面/封底设计语言(默认 IKB 满屏 + ASCII 呼吸场):
|
||
- section 用 .slide.accent (满屏 IKB,不是 light 白底)
|
||
- canvas-card 内首位插入 <canvas class="ascii-bg" aria-hidden="true">,本文件底部 IIFE 自动启动
|
||
- 主标题反白 weight 200,强调字用斜体而非 var(--accent)(底已是蓝,蓝压蓝看不见)
|
||
- 不要再放编号大字"01"——chrome-min 已经标 01/NN -->
|
||
<section class="slide accent" data-animate="hero">
|
||
<div class="canvas-card">
|
||
<canvas class="ascii-bg" aria-hidden="true"></canvas>
|
||
<div class="chrome-min">
|
||
<div class="l">[必填] Deck 标题 · Issue/Field Note 编号</div>
|
||
<div class="r">SS · 25.05.10 · 01 / NN</div>
|
||
</div>
|
||
|
||
<!-- 主体:padding 必须为 0,不要再叠 5vw,否则左右对不齐 chrome-min -->
|
||
<div style="flex:1;padding:0;display:grid;grid-template-rows:auto 1fr auto;gap:2.6vh">
|
||
<div data-anim="kicker" class="t-meta" style="color:rgba(255,255,255,.78);letter-spacing:.22em">[必填] 章节英文 / Section En</div>
|
||
|
||
<h1 data-anim="title" style="align-self:center;font-family:var(--sans),var(--sans-zh);font-weight:200;font-size:min(11.6vw,19vh);line-height:.94;letter-spacing:-.025em;color:#fff">[必填] 中文主标题<br/>(≤ 12 字,可在某字加 <span style="font-style:italic;font-weight:300">italic</span> 微强调)</h1>
|
||
|
||
<div data-anim="bottom" style="display:grid;grid-template-rows:auto auto;gap:1.6vh;border-top:1px solid rgba(255,255,255,.22);padding-top:2vh">
|
||
<div data-anim="lead" class="lead" style="max-width:52ch;color:rgba(255,255,255,.86)">[必填] 一段 1-2 行的副标 / 引子,定调全场.</div>
|
||
<div style="display:flex;justify-content:space-between;align-items:end">
|
||
<div class="t-meta" style="color:rgba(255,255,255,.6)">[选填] 作者 · 日期 · 出处</div>
|
||
<div class="t-meta" style="color:rgba(255,255,255,.6)">→ swipe / arrow keys</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ============ 示例:最后一页 · Closing Manifesto · 左 IKB+ASCII / 右白底 takeaway ============
|
||
与封面 IKB 首尾呼应,但收束更克制:左半保留 ASCII 呼吸场承载宣言,右半白底列 3 条 takeaway.
|
||
第 03 条用 var(--accent) IKB 蓝强调("单一锚点"原则),首尾形成"色彩闭环". -->
|
||
<section class="slide split" data-animate="split-statement">
|
||
<div class="canvas-card">
|
||
<div class="split-half">
|
||
<!-- 左半 · IKB 宣言 + ASCII 呼吸场 -->
|
||
<div class="half b-accent" style="padding:5.6vh 3.6vw 4.4vh;justify-content:space-between;position:relative;overflow:hidden">
|
||
<canvas class="ascii-bg" aria-hidden="true"></canvas>
|
||
<div class="chrome-min" style="margin-bottom:0;position:relative;z-index:1">
|
||
<div class="l">NN / NN</div>
|
||
<div class="r">CLOSING</div>
|
||
</div>
|
||
|
||
<div data-anim="manifesto" style="display:flex;flex-direction:column;gap:2vh;position:relative;z-index:1">
|
||
<div class="t-meta" style="color:rgba(255,255,255,.78);letter-spacing:.22em;margin-bottom:1.6vh">MANIFESTO</div>
|
||
<h2 style="font-family:var(--sans),var(--sans-zh);font-size:min(8vw,14vh);line-height:.94;letter-spacing:-.025em;font-weight:200;color:#fff">[必填] Build a model.<br/>Run <span style="font-style:italic;font-weight:300">forever</span>.</h2>
|
||
<div style="font-family:var(--sans),var(--sans-zh);font-size:max(14px,1vw);line-height:1.6;color:rgba(255,255,255,.82);font-weight:400;max-width:36ch;margin-top:1.4vh">[必填] 一句 1-2 行的中文/英文注脚,把宣言落地.</div>
|
||
</div>
|
||
|
||
<div data-anim="signature" style="display:flex;justify-content:space-between;align-items:end;border-top:1px solid rgba(255,255,255,.22);padding-top:2vh;position:relative;z-index:1">
|
||
<div class="t-meta" style="color:rgba(255,255,255,.62)">[选填] 作者 · 头衔</div>
|
||
<div class="t-meta" style="color:rgba(255,255,255,.62)">YY.MM.DD</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 右半 · 三条 takeaway · 白底承载理性收束 -->
|
||
<div class="half" style="padding:5.6vh 3.6vw 4.4vh;justify-content:space-between">
|
||
<div class="chrome-min">
|
||
<div class="l">TAKEAWAYS</div>
|
||
<div class="r">03 RULES</div>
|
||
</div>
|
||
|
||
<div data-anim="rules" style="display:flex;flex-direction:column;gap:0">
|
||
<div style="display:grid;grid-template-columns:auto 1fr;gap:2vw;align-items:start;padding:2.6vh 0;border-top:1px solid var(--border-subtle)">
|
||
<div style="font-family:var(--sans);font-weight:200;font-size:min(4.4vw,7.8vh);line-height:.9;color:var(--text-primary)">01</div>
|
||
<div>
|
||
<h3 style="font-family:var(--sans),var(--sans-zh);font-weight:400;font-size:max(18px,1.8vw);line-height:1.2;letter-spacing:-.015em;color:var(--text-primary);margin-bottom:1vh">[必填] takeaway 标题 01</h3>
|
||
<p style="font-family:var(--sans),var(--sans-zh);font-size:max(16px,.94vw);line-height:1.6;color:var(--text-secondary);font-weight:400">[必填] 1-2 行展开说明.</p>
|
||
</div>
|
||
</div>
|
||
<div style="display:grid;grid-template-columns:auto 1fr;gap:2vw;align-items:start;padding:2.6vh 0;border-top:1px solid var(--border-subtle)">
|
||
<div style="font-family:var(--sans);font-weight:200;font-size:min(4.4vw,7.8vh);line-height:.9;color:var(--text-primary)">02</div>
|
||
<div>
|
||
<h3 style="font-family:var(--sans),var(--sans-zh);font-weight:400;font-size:max(18px,1.8vw);line-height:1.2;letter-spacing:-.015em;color:var(--text-primary);margin-bottom:1vh">[必填] takeaway 标题 02</h3>
|
||
<p style="font-family:var(--sans),var(--sans-zh);font-size:max(16px,.94vw);line-height:1.6;color:var(--text-secondary);font-weight:400">[必填] 1-2 行展开说明.</p>
|
||
</div>
|
||
</div>
|
||
<div style="display:grid;grid-template-columns:auto 1fr;gap:2vw;align-items:start;padding:2.6vh 0;border-top:1px solid var(--border-subtle);border-bottom:2px solid var(--accent)">
|
||
<div style="font-family:var(--sans);font-weight:200;font-size:min(4.4vw,7.8vh);line-height:.9;color:var(--accent)">03</div>
|
||
<div>
|
||
<h3 style="font-family:var(--sans),var(--sans-zh);font-weight:400;font-size:max(18px,1.8vw);line-height:1.2;letter-spacing:-.015em;color:var(--accent);margin-bottom:1vh">[必填] takeaway 标题 03 · accent 强调</h3>
|
||
<p style="font-family:var(--sans),var(--sans-zh);font-size:max(16px,.94vw);line-height:1.6;color:var(--text-secondary);font-weight:400">[必填] 最后一条用 IKB 强调,与封面色彩首尾闭环.</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div data-anim="foot" class="t-meta" style="color:var(--text-helper);text-align:right">→ 完 · END OF FIELD NOTE</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
</div>
|
||
|
||
<div id="nav"></div>
|
||
|
||
<script>
|
||
/* =============== WebGL 网格背景 (瑞士风专用) ===============
|
||
极简移动网格 + 微弱点阵叠加,营造"工业感、精准感"
|
||
- 主网格: 缓慢漂移的细线网格
|
||
- 次级: 鼠标附近的极细点阵微扰
|
||
- 颜色: 跟随主题(浅底深线 / 深底亮线),配合 mix-blend-mode
|
||
*/
|
||
const VS = `attribute vec2 position;void main(){gl_Position=vec4(position,0.0,1.0);}`;
|
||
|
||
const FS = `precision highp float;
|
||
uniform vec2 u_resolution;
|
||
uniform float u_time;
|
||
uniform vec2 u_mouse;
|
||
uniform float u_dark; // 0 = light, 1 = dark
|
||
uniform vec3 u_accent;
|
||
|
||
float gridLine(vec2 uv, float spacing, float thickness){
|
||
vec2 g = abs(fract(uv / spacing) - 0.5);
|
||
float d = min(g.x, g.y);
|
||
return 1.0 - smoothstep(thickness - 0.005, thickness + 0.005, d);
|
||
}
|
||
|
||
float dot2(vec2 p){ return dot(p,p); }
|
||
|
||
void main(){
|
||
vec2 uv = gl_FragCoord.xy / u_resolution.xy;
|
||
float aspect = u_resolution.x / u_resolution.y;
|
||
vec2 p = uv;
|
||
p.x *= aspect;
|
||
|
||
// 缓慢平移
|
||
vec2 drift = vec2(u_time * 0.008, u_time * 0.005);
|
||
vec2 gp = p + drift;
|
||
|
||
// 主细网格 (大间距)
|
||
float mainGrid = gridLine(gp, 0.12, 0.012);
|
||
// 次级网格 (更细更密)
|
||
float subGrid = gridLine(gp, 0.024, 0.04) * 0.4;
|
||
|
||
// 鼠标附近的强化
|
||
vec2 m = u_mouse;
|
||
m.x *= aspect;
|
||
float md = length(p - m);
|
||
float mInfluence = exp(-md * 4.0) * 0.5;
|
||
|
||
float gridStrength = (mainGrid + subGrid * 0.5) * (0.45 + mInfluence);
|
||
|
||
// 点阵 (作为基底)
|
||
vec2 dotGrid = fract(gp * 50.0) - 0.5;
|
||
float dotMask = 1.0 - smoothstep(0.05, 0.14, length(dotGrid));
|
||
// 用低频噪声调制点阵密度
|
||
float wave = sin(gp.x * 1.4 + u_time * 0.15) * cos(gp.y * 1.6 - u_time * 0.12);
|
||
dotMask *= smoothstep(-0.3, 0.6, wave) * 0.6;
|
||
|
||
// 颜色: 浅底用深线条,深底用浅线条;高亮处带 accent 痕迹
|
||
vec3 lineColor = mix(vec3(0.08), vec3(0.92), u_dark);
|
||
vec3 bgColor = mix(vec3(0.97, 0.97, 0.96), vec3(0.06, 0.06, 0.07), u_dark);
|
||
|
||
// accent 暗示 (鼠标附近偷渡一点 accent 色)
|
||
vec3 col = bgColor;
|
||
col = mix(col, lineColor, gridStrength * 0.55);
|
||
col = mix(col, lineColor, dotMask * 0.35);
|
||
col = mix(col, u_accent, mInfluence * 0.18);
|
||
|
||
gl_FragColor = vec4(col, 1.0);
|
||
}`;
|
||
|
||
const mouse={x:0.5,y:0.5};
|
||
addEventListener('mousemove',e=>{mouse.x=e.clientX/innerWidth;mouse.y=1-e.clientY/innerHeight});
|
||
|
||
function bootGL(canvasId, fsSrc){
|
||
const canvas=document.getElementById(canvasId);
|
||
const gl=canvas.getContext('webgl',{alpha:true,antialias:true,premultipliedAlpha:false});
|
||
if(!gl) return ()=>false;
|
||
const mk=(t,s)=>{const sh=gl.createShader(t);gl.shaderSource(sh,s);gl.compileShader(sh);return sh};
|
||
const prog=gl.createProgram();
|
||
gl.attachShader(prog,mk(gl.VERTEX_SHADER,VS));
|
||
gl.attachShader(prog,mk(gl.FRAGMENT_SHADER,fsSrc));
|
||
gl.linkProgram(prog);gl.useProgram(prog);
|
||
const buf=gl.createBuffer();
|
||
gl.bindBuffer(gl.ARRAY_BUFFER,buf);
|
||
gl.bufferData(gl.ARRAY_BUFFER,new Float32Array([-1,-1,1,-1,-1,1,-1,1,1,-1,1,1]),gl.STATIC_DRAW);
|
||
const pos=gl.getAttribLocation(prog,'position');
|
||
gl.enableVertexAttribArray(pos);gl.vertexAttribPointer(pos,2,gl.FLOAT,false,0,0);
|
||
const lRes=gl.getUniformLocation(prog,'u_resolution');
|
||
const lT=gl.getUniformLocation(prog,'u_time');
|
||
const lM=gl.getUniformLocation(prog,'u_mouse');
|
||
const lD=gl.getUniformLocation(prog,'u_dark');
|
||
const lA=gl.getUniformLocation(prog,'u_accent');
|
||
const resize=()=>{
|
||
const d=Math.min(window.devicePixelRatio||1,2);
|
||
canvas.width=innerWidth*d;canvas.height=innerHeight*d;
|
||
gl.viewport(0,0,canvas.width,canvas.height);
|
||
};
|
||
addEventListener('resize',resize);resize();
|
||
|
||
// 读取 CSS 变量,把 accent 颜色塞进 shader
|
||
function readAccent(){
|
||
const cs = getComputedStyle(document.documentElement);
|
||
const hex = cs.getPropertyValue('--accent').trim() || '#002FA7';
|
||
const m = hex.match(/^#([0-9a-f]{6})$/i);
|
||
if(!m) return [0, 0.18, 0.65];
|
||
const n = parseInt(m[1], 16);
|
||
return [((n>>16)&255)/255, ((n>>8)&255)/255, (n&255)/255];
|
||
}
|
||
let accent = readAccent();
|
||
let dark = 0;
|
||
|
||
return (tSec, isDark)=>{
|
||
if(isDark !== undefined) dark = isDark ? 1 : 0;
|
||
accent = readAccent();
|
||
gl.uniform2f(lRes,canvas.width,canvas.height);
|
||
gl.uniform1f(lT,tSec);
|
||
gl.uniform2f(lM,mouse.x,mouse.y);
|
||
gl.uniform1f(lD,dark);
|
||
gl.uniform3f(lA,accent[0],accent[1],accent[2]);
|
||
gl.drawArrays(gl.TRIANGLES,0,6);
|
||
return true;
|
||
};
|
||
}
|
||
// canvas-mode / low-power: skip WebGL draw loop (no active RAF loop)
|
||
let darkMode=false;
|
||
let gridCtrl=null, gridRAF=0, gridT0=Date.now();
|
||
function startGrid(){
|
||
if(document.body.classList.contains('canvas-mode') || window.__lowPowerMode || gridRAF) return;
|
||
if(!gridCtrl) gridCtrl = bootGL('bg-grid',FS);
|
||
if(!gridCtrl) return;
|
||
gridT0=Date.now();
|
||
function loop(){
|
||
if(window.__lowPowerMode){gridRAF=0;return;}
|
||
const t=(Date.now()-gridT0)/1000;
|
||
gridCtrl(t, darkMode);
|
||
gridRAF=requestAnimationFrame(loop);
|
||
}
|
||
gridRAF=requestAnimationFrame(loop);
|
||
}
|
||
function stopGrid(){
|
||
if(gridRAF) cancelAnimationFrame(gridRAF);
|
||
gridRAF=0;
|
||
}
|
||
if(document.body.classList.contains('canvas-mode')){
|
||
const c=document.getElementById('bg-grid');
|
||
if(c) c.remove();
|
||
}else{
|
||
startGrid();
|
||
}
|
||
addEventListener('swiss-low-power-change', e=>{e.detail.on ? stopGrid() : startGrid();});
|
||
|
||
// =============== 导航 ===============
|
||
const deck=document.getElementById('deck');
|
||
const slides=deck.querySelectorAll('.slide');
|
||
const nav=document.getElementById('nav');
|
||
let idx=0,total=slides.length,lock=false;
|
||
|
||
deck.style.width=(total*100)+'vw';
|
||
|
||
slides.forEach((s,i)=>{
|
||
const b=document.createElement('button');
|
||
b.className='dot';b.dataset.i=i;b.setAttribute('aria-label','Page '+(i+1));
|
||
b.onclick=()=>go(i);
|
||
nav.appendChild(b);
|
||
});
|
||
|
||
function go(n){
|
||
if(lock)return;
|
||
idx=Math.max(0,Math.min(total-1,n));
|
||
window.__currentSlideIndex = idx;
|
||
deck.style.transform=`translateX(${-idx*100}vw)`;
|
||
nav.querySelectorAll('.dot').forEach((d,i)=>d.classList.toggle('active',i===idx));
|
||
const el=slides[idx];
|
||
const isDark = el.classList.contains('dark') || el.classList.contains('accent');
|
||
document.body.classList.toggle('dark-bg', isDark);
|
||
darkMode = isDark;
|
||
if(window.__playSlide) setTimeout(()=>window.__playSlide(idx), 450);
|
||
lock=true;setTimeout(()=>lock=false,700);
|
||
}
|
||
|
||
/* =============== ESC 索引视图 =============== */
|
||
let overviewOn=false;
|
||
const ov=document.createElement('div');
|
||
ov.id='overview';
|
||
ov.style.cssText='position:fixed;inset:0;z-index:100;background:rgba(250,250,248,.96);backdrop-filter:blur(12px);display:none;overflow-y:auto;padding:4vh 4vw';
|
||
document.body.appendChild(ov);
|
||
|
||
function buildOverview(){
|
||
ov.innerHTML='';
|
||
const grid=document.createElement('div');
|
||
grid.style.cssText='display:grid;grid-template-columns:repeat(4,1fr);gap:2vh 1.6vw;max-width:90vw;margin:0 auto';
|
||
slides.forEach((s,i)=>{
|
||
const card=document.createElement('div');
|
||
card.style.cssText='cursor:pointer;overflow:hidden;border:2px solid '+(i===idx?'var(--accent)':'rgba(0,0,0,.12)')+';transition:border-color .2s';
|
||
card.onmouseenter=()=>card.style.borderColor='rgba(0,0,0,.4)';
|
||
card.onmouseleave=()=>card.style.borderColor=i===idx?'var(--accent)':'rgba(0,0,0,.12)';
|
||
const wrap=document.createElement('div');
|
||
const isDark = s.classList.contains('dark') || s.classList.contains('accent');
|
||
wrap.style.cssText='width:100%;aspect-ratio:16/9;overflow:hidden;position:relative;pointer-events:none;background:'+(isDark?'var(--ink)':'var(--paper)');
|
||
const clone=s.cloneNode(true);
|
||
clone.style.cssText='width:100vw;height:100vh;transform:scale('+(1/4.5)+');transform-origin:top left;position:absolute;top:0;left:0;pointer-events:none';
|
||
wrap.appendChild(clone);
|
||
const label=document.createElement('div');
|
||
/* ESC 索引卡 label */
|
||
label.style.cssText='padding:6px 10px;font-family:var(--mono);font-size:14px;letter-spacing:.14em;text-transform:uppercase;color:var(--ink);opacity:.7';
|
||
label.textContent=(i+1)+' / '+total;
|
||
card.appendChild(wrap);
|
||
card.appendChild(label);
|
||
card.onclick=()=>{toggleOverview();go(i)};
|
||
grid.appendChild(card);
|
||
});
|
||
ov.appendChild(grid);
|
||
}
|
||
|
||
function toggleOverview(){
|
||
overviewOn=!overviewOn;
|
||
if(overviewOn){buildOverview();ov.style.display='block';}
|
||
else{ov.style.display='none';}
|
||
}
|
||
|
||
addEventListener('keydown',e=>{
|
||
if(e.key==='Escape'){e.preventDefault();toggleOverview();return;}
|
||
if(e.key && e.key.toLowerCase()==='b' && !e.metaKey && !e.ctrlKey && !e.altKey){
|
||
e.preventDefault();
|
||
window.__setLowPowerMode(!window.__lowPowerMode);
|
||
return;
|
||
}
|
||
if(overviewOn)return;
|
||
if(e.key==='ArrowRight'||e.key==='PageDown'||e.key===' '||e.key==='ArrowDown'){
|
||
if(window.__pipeAdvance && window.__pipeAdvance()) return;
|
||
go(idx+1);
|
||
return;
|
||
}
|
||
if(e.key==='ArrowLeft'||e.key==='PageUp'||e.key==='ArrowUp')go(idx-1);
|
||
if(e.key==='Home')go(0);
|
||
if(e.key==='End')go(total-1);
|
||
});
|
||
|
||
let wheelTO=null,wheelAcc=0;
|
||
addEventListener('wheel',e=>{
|
||
wheelAcc+=e.deltaY+e.deltaX;
|
||
if(Math.abs(wheelAcc)>50){
|
||
if(wheelAcc>0 && window.__pipeAdvance && window.__pipeAdvance()){
|
||
wheelAcc=0;
|
||
}else{
|
||
go(idx+(wheelAcc>0?1:-1));wheelAcc=0;
|
||
}
|
||
}
|
||
clearTimeout(wheelTO);wheelTO=setTimeout(()=>wheelAcc=0,150);
|
||
},{passive:true});
|
||
|
||
let tx=0,ty=0;
|
||
addEventListener('touchstart',e=>{tx=e.touches[0].clientX;ty=e.touches[0].clientY},{passive:true});
|
||
addEventListener('touchend',e=>{
|
||
const dx=(e.changedTouches[0].clientX-tx);
|
||
const dy=(e.changedTouches[0].clientY-ty);
|
||
if(Math.abs(dx)>50&&Math.abs(dx)>Math.abs(dy)){
|
||
if(dx<0 && window.__pipeAdvance && window.__pipeAdvance()) return;
|
||
go(idx+(dx<0?1:-1));
|
||
}
|
||
},{passive:true});
|
||
|
||
const initialSlideParam = new URLSearchParams(location.search).get('slide');
|
||
const initialSlide = initialSlideParam ? Number(initialSlideParam) - 1 : 0;
|
||
go(Number.isFinite(initialSlide) ? initialSlide : 0);
|
||
</script>
|
||
<script src="https://unpkg.com/lucide@latest/dist/umd/lucide.min.js"></script>
|
||
<script>lucide.createIcons();</script>
|
||
|
||
<!-- Motion One 动效引擎 (与原模板一致) -->
|
||
<script type="module">
|
||
let motion;
|
||
try {
|
||
motion = await import('./assets/motion.min.js');
|
||
} catch(e1) {
|
||
try {
|
||
motion = await import('https://cdn.jsdelivr.net/npm/motion@11.11.17/+esm');
|
||
} catch(e2) {
|
||
console.warn('[motion] local + CDN both failed, disabling animations', e1, e2);
|
||
document.querySelectorAll('[data-anim]').forEach(el=>{el.style.opacity='1';el.style.transform='none'});
|
||
document.querySelectorAll('[data-animate="pipeline"] [data-anim]').forEach(el=>el.style.opacity='1');
|
||
}
|
||
}
|
||
|
||
if(motion){
|
||
const { animate } = motion;
|
||
document.body.classList.add('motion-ready');
|
||
|
||
/* ============================================================
|
||
IBM Carbon Motion · 每个 recipe 服务一种表达
|
||
不是一刀切的 stagger,而是把动效绑在内容语义上
|
||
============================================================ */
|
||
const EASE_PROD = [.2, 0, .38, .9];
|
||
const EASE_ENTRY_EXP = [0, 0, .3, 1];
|
||
|
||
const slides = [...document.querySelectorAll('.slide')];
|
||
let lastIdx = -1;
|
||
|
||
function resetAnims(slide){
|
||
slide.querySelectorAll('[data-anim]').forEach(el=>{
|
||
el.style.opacity='';
|
||
el.style.transform='';
|
||
});
|
||
/* 同时复位需要被 recipe 接管的元素 */
|
||
slide.querySelectorAll('.row-fill,.tl-node,.stack-block,.bar-tower,.sub-card,.col,.vrule,.kpi-cell')
|
||
.forEach(el=>{el.style.cssText = el.dataset._origCss || el.style.cssText;});
|
||
}
|
||
|
||
/* ---------- 通用工具 ---------- */
|
||
const fade = (el, opts={})=>animate(el,
|
||
{opacity:[0,1], y:[opts.y ?? 12, 0]},
|
||
{duration:opts.duration ?? .6, delay:opts.delay ?? 0,
|
||
easing:opts.easing ?? EASE_ENTRY_EXP});
|
||
|
||
/* ---------- recipe: hero · 封面索引 ----------
|
||
大编号一个个亮起 → 索引行最后落定 */
|
||
function rHero(slide, all){
|
||
const numRows = [...slide.querySelectorAll('.cover-row')];
|
||
const rest = all.filter(el=>!numRows.length || el !== numRows[0]);
|
||
/* 先入: chrome 240ms */
|
||
const chrome = slide.querySelector('.chrome-min');
|
||
if(chrome) animate(chrome, {opacity:[0,1]}, {duration:.24, easing:EASE_PROD});
|
||
/* 大编号 01/02/03 像点名一样依次亮 */
|
||
numRows.forEach((row, i)=>{
|
||
animate(row, {opacity:[0,1], x:[-12,0]},
|
||
{duration:.5, delay:.15 + i*.18, easing:EASE_ENTRY_EXP});
|
||
});
|
||
/* 索引底栏最后慢慢落定 */
|
||
const idx = slide.querySelector('[data-anim="line"]');
|
||
if(idx) fade(idx, {delay:.15 + numRows.length*.18 + .1, duration:.5, y:6});
|
||
}
|
||
|
||
/* ---------- recipe: progression · 1× → 10× → 1000× ----------
|
||
节点依次入场,每个节点的数字单独"递进生长"营造跃迁 */
|
||
function rProgression(slide, all){
|
||
const head = slide.querySelector('[data-anim="line"]');
|
||
if(head) fade(head, {duration:.6, y:10});
|
||
|
||
const nodes = [...slide.querySelectorAll('.tl-node')];
|
||
nodes.forEach((node, i)=>{
|
||
const base = .35 + i*.32; /* 节点之间间隔大,营造时间感 */
|
||
/* 整个节点先轻微浮入 */
|
||
animate(node, {opacity:[0,1], y:[14, 0]},
|
||
{duration:.55, delay:base, easing:EASE_ENTRY_EXP});
|
||
/* 再让 multi(数字)从 .85 scale 弹到 1,延迟 100ms */
|
||
const multi = node.querySelector('.multi');
|
||
if(multi) animate(multi, {scale:[.92, 1], opacity:[0,1]},
|
||
{duration:.5, delay:base + .12, easing:EASE_ENTRY_EXP});
|
||
});
|
||
|
||
/* 底部 KPI 4 列最后落定,内部 60ms stagger */
|
||
const kpis = [...slide.querySelectorAll('.kpi-cell')];
|
||
kpis.forEach((cell, i)=>{
|
||
animate(cell, {opacity:[0,1], y:[8, 0]},
|
||
{duration:.4, delay:1.4 + i*.07, easing:EASE_PROD});
|
||
});
|
||
}
|
||
|
||
/* ---------- recipe: statement · 大宣言 ----------
|
||
左半屏标题逐行落下,右半屏 leaked 信息晚 600ms 进 */
|
||
function rStatement(slide, all){
|
||
const halves = [...slide.querySelectorAll('.half')];
|
||
if(halves.length === 2){
|
||
animate(halves[0], {opacity:[0,1], y:[18,0]},
|
||
{duration:.7, delay:0, easing:EASE_ENTRY_EXP});
|
||
animate(halves[1], {opacity:[0,1], y:[18,0]},
|
||
{duration:.7, delay:.6, easing:EASE_ENTRY_EXP});
|
||
} else {
|
||
/* P9 Index Card — 三行像盖章一样依次落 */
|
||
const head = slide.querySelector('[data-anim="line"]');
|
||
if(head) fade(head, {duration:.5, y:6});
|
||
const blocks = all.filter(el=>el !== head);
|
||
blocks.forEach((el, i)=>{
|
||
animate(el, {opacity:[0,1], y:[20,0]},
|
||
{duration:.55, delay:.25 + i*.18, easing:EASE_ENTRY_EXP});
|
||
});
|
||
}
|
||
}
|
||
|
||
/* ---------- recipe: grid-reveal · 五个定义 ----------
|
||
卡片按 nb-corner 序号 01→02→03→04→05→Σ 依次揭示 */
|
||
function rGridReveal(slide, all){
|
||
const head = slide.querySelector('[data-anim="line"]');
|
||
if(head) fade(head, {duration:.6, y:10});
|
||
const cards = [...slide.querySelectorAll('.sub-card')];
|
||
cards.forEach((card, i)=>{
|
||
animate(card, {opacity:[0,1], y:[20,0], scale:[.96, 1]},
|
||
{duration:.5, delay:.3 + i*.09, easing:EASE_ENTRY_EXP});
|
||
});
|
||
}
|
||
|
||
/* ---------- recipe: stack-build · 三层架构 ----------
|
||
中间 thin 先入 → 上层 fat skills 从顶推下 → 下层 application 从底推上 */
|
||
function rStackBuild(slide, all){
|
||
const head = slide.querySelector('[data-anim="line"]');
|
||
if(head) fade(head, {duration:.6, y:10});
|
||
|
||
const blocks = [...slide.querySelectorAll('.stack-block')];
|
||
/* 先入: 中间薄层(LAYER 02) */
|
||
if(blocks[1]) animate(blocks[1], {opacity:[0,1], scaleY:[.85, 1]},
|
||
{duration:.55, delay:.3, easing:EASE_ENTRY_EXP});
|
||
/* 上推下: LAYER 01 fat skills 从顶部 push down */
|
||
if(blocks[0]) animate(blocks[0], {opacity:[0,1], y:[-22, 0]},
|
||
{duration:.6, delay:.6, easing:EASE_ENTRY_EXP});
|
||
/* 下推上: LAYER 03 application 从底部 push up */
|
||
if(blocks[2]) animate(blocks[2], {opacity:[0,1], y:[22, 0]},
|
||
{duration:.6, delay:.6, easing:EASE_ENTRY_EXP});
|
||
|
||
const foot = slide.querySelector('.t-meta');
|
||
if(foot) animate(foot, {opacity:[0,1]}, {duration:.3, delay:1.3, easing:EASE_PROD});
|
||
}
|
||
|
||
/* ---------- recipe: measure-up · YC KPI 塔 ----------
|
||
塔从底部 scaleY 0→1 生长 + 数字最后弹入 */
|
||
function rMeasureUp(slide, all){
|
||
const head = slide.querySelector('[data-anim="line"]');
|
||
if(head) fade(head, {duration:.6, y:10});
|
||
|
||
const towers = [...slide.querySelectorAll('.bar-tower')];
|
||
towers.forEach((tower, i)=>{
|
||
const block = tower.querySelector('.body-block');
|
||
if(block){
|
||
block.style.transformOrigin = 'bottom center';
|
||
animate(block, {opacity:[0,1], scaleY:[.05, 1]},
|
||
{duration:.7, delay:.35 + i*.12, easing:EASE_ENTRY_EXP});
|
||
}
|
||
/* cap (顶部图标) 等柱体长好后弹入 */
|
||
const cap = tower.querySelector('.cap');
|
||
if(cap) animate(cap, {opacity:[0,1], y:[-8, 0]},
|
||
{duration:.4, delay:.85 + i*.12, easing:EASE_PROD});
|
||
});
|
||
}
|
||
|
||
/* ---------- recipe: bar-grow · 90% 价值分布 ----------
|
||
标题先入 → hairline 从中点向两侧 stroke draw → bar 依次 width 0→target → 数值 fade in */
|
||
function rBarGrow(slide, all){
|
||
const head = slide.querySelector('[data-anim="line"]');
|
||
if(head) fade(head, {duration:.6, y:10});
|
||
|
||
/* 中部 hairline:从 100% width 0 拉到 100% (transformOrigin: center) */
|
||
const midRow = slide.querySelector('[data-anim="up"]');
|
||
if(midRow){
|
||
const midLabel = midRow.querySelector('.t-cat');
|
||
const midLine = midRow.querySelector('div[style*="height:1px"]');
|
||
if(midLabel) animate(midLabel, {opacity:[0,1], x:[-8,0]},
|
||
{duration:.4, delay:.4, easing:EASE_PROD});
|
||
if(midLine){
|
||
midLine.style.transformOrigin = 'center';
|
||
animate(midLine, {opacity:[0,1], scaleX:[0, 1]},
|
||
{duration:.55, delay:.5, easing:EASE_ENTRY_EXP});
|
||
}
|
||
}
|
||
|
||
/* bar 行依次 width 增长 */
|
||
const fills = [...slide.querySelectorAll('.row-fill')];
|
||
const labels = [...slide.querySelectorAll('.row-lbl')];
|
||
const values = [...slide.querySelectorAll('.row-val')];
|
||
fills.forEach((fill, i)=>{
|
||
const target = fill.style.width;
|
||
fill.style.width = '0%';
|
||
if(labels[i]) animate(labels[i], {opacity:[0,1], x:[-12,0]},
|
||
{duration:.4, delay:.85 + i*.14, easing:EASE_PROD});
|
||
animate(fill, {width:['0%', target]},
|
||
{duration:.65, delay:.95 + i*.14, easing:EASE_ENTRY_EXP});
|
||
if(values[i]) animate(values[i], {opacity:[0,1]},
|
||
{duration:.3, delay:1.5 + i*.14, easing:EASE_PROD});
|
||
});
|
||
}
|
||
|
||
/* ---------- recipe: duo-mirror · Latent vs Deterministic ----------
|
||
左 80ms 入,vrule 从中心 scaleY 0→1,右 240ms 入 */
|
||
function rDuoMirror(slide, all){
|
||
const head = slide.querySelector('[data-anim="line"]');
|
||
if(head) fade(head, {duration:.6, y:10});
|
||
|
||
const cols = [...slide.querySelectorAll('.duo-compare .col')];
|
||
const vrule = slide.querySelector('.duo-compare .vrule');
|
||
if(cols[0]) animate(cols[0], {opacity:[0,1], x:[-24, 0]},
|
||
{duration:.65, delay:.4, easing:EASE_ENTRY_EXP});
|
||
if(vrule){
|
||
vrule.style.transformOrigin = 'center';
|
||
animate(vrule, {opacity:[0,1], scaleY:[0, 1]},
|
||
{duration:.55, delay:.55, easing:EASE_ENTRY_EXP});
|
||
}
|
||
if(cols[1]) animate(cols[1], {opacity:[0,1], x:[24, 0]},
|
||
{duration:.65, delay:.7, easing:EASE_ENTRY_EXP});
|
||
|
||
const foot = slide.querySelector('.t-meta');
|
||
if(foot) animate(foot, {opacity:[0,1]}, {duration:.3, delay:1.3, easing:EASE_PROD});
|
||
}
|
||
|
||
/* ---------- recipe: split-statement · 收尾 ----------
|
||
左黑半屏的 once / forever 错位入场;右白半屏 takeaway list 后跟 */
|
||
function rSplitStatement(slide, all){
|
||
const halves = [...slide.querySelectorAll('.half')];
|
||
/* 左黑半屏 — once 先入,forever 间隔 600ms */
|
||
if(halves[0]){
|
||
animate(halves[0], {opacity:[0,1]}, {duration:.4, easing:EASE_PROD});
|
||
const kpis = halves[0].querySelectorAll('.kpi-thin');
|
||
kpis.forEach((k, i)=>{
|
||
animate(k, {opacity:[0,1], y:[24,0]},
|
||
{duration:.7, delay:.25 + i*.55, easing:EASE_ENTRY_EXP});
|
||
});
|
||
}
|
||
/* 右白半屏 — list 三条依次入,在左侧 once 出现后开始 */
|
||
if(halves[1]){
|
||
animate(halves[1], {opacity:[0,1]}, {duration:.4, delay:.3, easing:EASE_PROD});
|
||
const items = halves[1].querySelectorAll('.takeaway-list li');
|
||
items.forEach((li, i)=>{
|
||
animate(li, {opacity:[0,1], x:[20, 0]},
|
||
{duration:.45, delay:1.0 + i*.12, easing:EASE_ENTRY_EXP});
|
||
});
|
||
}
|
||
}
|
||
|
||
/* ---------- recipe: timeline-walk · P11 横向 evolution ----------
|
||
标题先入 → 横轴虚线 scaleX 拉开(伪) → 5 个 dot 按年代依次 scale 入 → label 跟随 */
|
||
function rTimelineWalk(slide, all){
|
||
const head = slide.querySelector('[data-anim="line"]');
|
||
if(head) fade(head, {duration:.55, y:10});
|
||
|
||
const tl = slide.querySelector('.timeline-h');
|
||
if(tl) animate(tl, {opacity:[0,1]}, {duration:.4, delay:.35, easing:EASE_PROD});
|
||
|
||
const nodes = [...slide.querySelectorAll('.timeline-h .th-node')];
|
||
nodes.forEach((node, i)=>{
|
||
const base = .55 + i*.18;
|
||
const dot = node.querySelector('.dot');
|
||
const label = node.querySelector('.label');
|
||
if(dot){
|
||
dot.style.transformOrigin='center';
|
||
animate(dot, {opacity:[0,1], scale:[.2, 1]},
|
||
{duration:.45, delay:base, easing:EASE_ENTRY_EXP});
|
||
}
|
||
if(label){
|
||
const fromY = node.classList.contains('up') ? 8 : -8;
|
||
/* 保留 CSS 的水平居中 translateX(-50%),避免动效覆盖后 label 与 dot 错位 */
|
||
animate(label, {opacity:[0,1], transform:[`translate(-50%, ${fromY}px)`, 'translate(-50%, 0px)']},
|
||
{duration:.5, delay:base + .12, easing:EASE_ENTRY_EXP});
|
||
}
|
||
});
|
||
|
||
const foot = slide.querySelector('.t-meta');
|
||
if(foot) animate(foot, {opacity:[0,1]}, {duration:.3, delay:1.7, easing:EASE_PROD});
|
||
}
|
||
|
||
/* ---------- recipe: manifesto · P12 Form & Found ----------
|
||
副标先入 → 大字两段错峰落 → 底部 ink 通栏条从下推上 */
|
||
function rManifesto(slide, all){
|
||
const head = slide.querySelector('[data-anim="line"]');
|
||
if(head){
|
||
const cat = head.querySelector('.t-cat');
|
||
const title = head.querySelector('div:nth-child(2)');
|
||
if(cat) animate(cat, {opacity:[0,1], x:[-10,0]},
|
||
{duration:.4, delay:.1, easing:EASE_PROD});
|
||
if(title) animate(title, {opacity:[0,1], y:[26, 0]},
|
||
{duration:.85, delay:.3, easing:EASE_ENTRY_EXP});
|
||
}
|
||
/* 底部 ink 条从下推入 */
|
||
const foot = [...slide.querySelectorAll('[data-anim="up"]')];
|
||
foot.forEach((el, i)=>{
|
||
animate(el, {opacity:[0,1], y:[40, 0]},
|
||
{duration:.75, delay:.85 + i*.12, easing:EASE_ENTRY_EXP});
|
||
});
|
||
}
|
||
|
||
/* ---------- recipe: three-forces · P13 ----------
|
||
左 ink hero 先入 → 右 3 张卡按 1/2/3 依次从右滑入 + 每张大数字单独弹入 */
|
||
function rThreeForces(slide, all){
|
||
const head = slide.querySelector('[data-anim="line"]');
|
||
if(head) fade(head, {duration:.5, y:8});
|
||
|
||
const grid = slide.querySelector('[data-anim="up"]');
|
||
if(grid) animate(grid, {opacity:[0,1]}, {duration:.3, delay:.3, easing:EASE_PROD});
|
||
|
||
const heroBlock = grid?.querySelector(':scope > div:first-child');
|
||
if(heroBlock) animate(heroBlock, {opacity:[0,1], x:[-26, 0]},
|
||
{duration:.6, delay:.4, easing:EASE_ENTRY_EXP});
|
||
|
||
const cards = grid ? [...grid.querySelectorAll(':scope > div:nth-child(2) > .card-fill')] : [];
|
||
cards.forEach((card, i)=>{
|
||
const base = .6 + i*.18;
|
||
animate(card, {opacity:[0,1], x:[28, 0]},
|
||
{duration:.6, delay:base, easing:EASE_ENTRY_EXP});
|
||
const num = card.querySelector(':scope > div:first-child');
|
||
if(num) animate(num, {opacity:[0,1], scale:[.7, 1]},
|
||
{duration:.5, delay:base + .15, easing:EASE_ENTRY_EXP});
|
||
});
|
||
}
|
||
|
||
/* ---------- recipe: loop-form · P14 自学闭环 ----------
|
||
左 4 步像台阶依次入 → 右环图节点按时钟顺序入 → 中心 improves scale 入 */
|
||
function rLoopForm(slide, all){
|
||
const head = slide.querySelector('[data-anim="line"]');
|
||
if(head) fade(head, {duration:.55, y:10});
|
||
|
||
const grid = slide.querySelector('[data-anim="up"]');
|
||
if(grid) animate(grid, {opacity:[0,1]}, {duration:.3, delay:.35, easing:EASE_PROD});
|
||
|
||
/* 左侧 4 步台阶,每步从左滑入 */
|
||
const steps = grid ? [...grid.querySelectorAll(':scope > div:first-child > div')] : [];
|
||
steps.forEach((step, i)=>{
|
||
animate(step, {opacity:[0,1], x:[-18, 0]},
|
||
{duration:.5, delay:.5 + i*.14, easing:EASE_ENTRY_EXP});
|
||
});
|
||
|
||
/* 右侧 SVG 节点 (4 个 circle + label) 按 01→04 顺序入 */
|
||
const svg = grid?.querySelector('svg');
|
||
if(svg){
|
||
const ring = svg.querySelector('circle:first-of-type');
|
||
if(ring) animate(ring, {opacity:[0,.25]}, {duration:.5, delay:.6, easing:EASE_PROD});
|
||
|
||
const nodeCircles = [...svg.querySelectorAll('circle')].slice(1);
|
||
nodeCircles.forEach((c, i)=>{
|
||
c.style.transformOrigin = `${c.getAttribute('cx')}px ${c.getAttribute('cy')}px`;
|
||
animate(c, {opacity:[0,1], scale:[.4, 1]},
|
||
{duration:.45, delay:.7 + i*.16, easing:EASE_ENTRY_EXP});
|
||
});
|
||
|
||
const arrows = [...svg.querySelectorAll('path[marker-end]')];
|
||
arrows.forEach((p, i)=>{
|
||
animate(p, {opacity:[0,1]},
|
||
{duration:.4, delay:.85 + i*.16, easing:EASE_PROD});
|
||
});
|
||
|
||
const center = [...svg.querySelectorAll('text')].slice(-2);
|
||
center.forEach((t, i)=>{
|
||
animate(t, {opacity:[0,1], scale:[.7, 1]},
|
||
{duration:.5, delay:1.55 + i*.1, easing:EASE_ENTRY_EXP});
|
||
});
|
||
}
|
||
}
|
||
|
||
/* ---------- recipe: matrix-fill · P15 skill 矩阵 ----------
|
||
标题入 → 12 张卡按对角线波 (i+j) 扫入 → 底部 20,000 大数字最后 fade 入 */
|
||
function rMatrixFill(slide, all){
|
||
const head = slide.querySelector('[data-anim="line"]');
|
||
if(head) fade(head, {duration:.55, y:10});
|
||
|
||
const matrix = slide.querySelector('[data-anim="up"]');
|
||
if(!matrix) return;
|
||
animate(matrix, {opacity:[0,1]}, {duration:.3, delay:.35, easing:EASE_PROD});
|
||
|
||
const cards = [...matrix.children];
|
||
const cols = 6;
|
||
cards.forEach((card, i)=>{
|
||
const row = Math.floor(i/cols), col = i%cols;
|
||
const wave = (row + col) * .055;
|
||
animate(card, {opacity:[0,1], y:[14, 0], scale:[.92, 1]},
|
||
{duration:.42, delay:.5 + wave, easing:EASE_ENTRY_EXP});
|
||
});
|
||
|
||
/* 底部 20,000 区块 */
|
||
const foot = [...slide.querySelectorAll('[data-anim="up"]')][1];
|
||
if(foot){
|
||
animate(foot, {opacity:[0,1], y:[18, 0]},
|
||
{duration:.7, delay:1.4, easing:EASE_ENTRY_EXP});
|
||
const bigNum = foot.querySelector('div:nth-child(1) > div:nth-child(2)');
|
||
if(bigNum) animate(bigNum, {opacity:[0,1], scale:[.94, 1]},
|
||
{duration:.7, delay:1.55, easing:EASE_ENTRY_EXP});
|
||
}
|
||
}
|
||
|
||
/* ---------- recipe: field-notes · P16 散点观察 ----------
|
||
标题入 → 6 张卡按"散点"乱序延迟入,微小旋转复位 */
|
||
function rFieldNotes(slide, all){
|
||
const head = slide.querySelector('[data-anim="line"]');
|
||
if(head) fade(head, {duration:.55, y:10});
|
||
|
||
const grid = slide.querySelector('[data-anim="up"]');
|
||
if(!grid) return;
|
||
animate(grid, {opacity:[0,1]}, {duration:.3, delay:.35, easing:EASE_PROD});
|
||
|
||
/* 散点顺序: 用一个稍微打乱的索引数组,营造"乱中有序"感 */
|
||
const order = [0, 3, 1, 4, 2, 5];
|
||
const cards = [...grid.children];
|
||
order.forEach((idx, i)=>{
|
||
const card = cards[idx];
|
||
if(!card) return;
|
||
animate(card, {opacity:[0,1], y:[18, 0], rotate:[(idx%2?-.6:.6), 0]},
|
||
{duration:.55, delay:.5 + i*.11, easing:EASE_ENTRY_EXP});
|
||
});
|
||
}
|
||
|
||
/* ---------- recipe: system-diagram · P17 三圆系统图 ----------
|
||
标题入 → SVG 三组图依次入 + 中间同心圆从外向内 scale 入 → 下方注释列依次入 */
|
||
function rSystemDiagram(slide, all){
|
||
const head = slide.querySelector('[data-anim="line"]');
|
||
if(head) fade(head, {duration:.55, y:10});
|
||
|
||
const stage = slide.querySelector('[data-anim="up"]');
|
||
if(!stage) return;
|
||
animate(stage, {opacity:[0,1]}, {duration:.3, delay:.35, easing:EASE_PROD});
|
||
|
||
const svgs = [...stage.querySelectorAll('svg')];
|
||
svgs.forEach((svg, i)=>{
|
||
const base = .55 + i*.22;
|
||
const circles = [...svg.querySelectorAll('circle')];
|
||
/* 中间是同心圆: 从外圈到内圈依次 scale 入 */
|
||
if(circles.length > 1){
|
||
circles.forEach((c, j)=>{
|
||
c.style.transformOrigin = `${c.getAttribute('cx')}px ${c.getAttribute('cy')}px`;
|
||
animate(c, {opacity:[0,1], scale:[.4, 1]},
|
||
{duration:.5, delay:base + j*.13, easing:EASE_ENTRY_EXP});
|
||
});
|
||
} else if(circles[0]){
|
||
circles[0].style.transformOrigin = `${circles[0].getAttribute('cx')}px ${circles[0].getAttribute('cy')}px`;
|
||
animate(circles[0], {opacity:[0,1], scale:[.4, 1]},
|
||
{duration:.5, delay:base, easing:EASE_ENTRY_EXP});
|
||
}
|
||
const labels = [...svg.querySelectorAll('text')];
|
||
labels.forEach((t, j)=>{
|
||
animate(t, {opacity:[0,1]},
|
||
{duration:.4, delay:base + .25 + j*.06, easing:EASE_PROD});
|
||
});
|
||
});
|
||
|
||
/* 下方注释列 */
|
||
const cols = [...stage.querySelectorAll(':scope > div:last-child > div')];
|
||
cols.forEach((col, i)=>{
|
||
animate(col, {opacity:[0,1], y:[12, 0]},
|
||
{duration:.45, delay:1.3 + i*.1, easing:EASE_ENTRY_EXP});
|
||
});
|
||
}
|
||
|
||
/* ---------- recipe: why-now · P18 三列 + 巨大底数 ----------
|
||
标题入 → 三列文本入 → 三个底部巨数 01/02/03 错峰 scale 落定 */
|
||
function rWhyNow(slide, all){
|
||
const head = slide.querySelector('[data-anim="line"]');
|
||
if(head) fade(head, {duration:.55, y:10});
|
||
|
||
const grid = slide.querySelector('[data-anim="up"]');
|
||
if(!grid) return;
|
||
animate(grid, {opacity:[0,1]}, {duration:.3, delay:.35, easing:EASE_PROD});
|
||
|
||
const cols = [...grid.children];
|
||
cols.forEach((col, i)=>{
|
||
const base = .5 + i*.16;
|
||
const body = col.querySelector(':scope > div:not(:last-child)');
|
||
const big = col.querySelector(':scope > div:last-child');
|
||
if(body) animate(body, {opacity:[0,1], y:[14, 0]},
|
||
{duration:.55, delay:base, easing:EASE_ENTRY_EXP});
|
||
if(big) animate(big, {opacity:[0,1], scale:[.7, 1]},
|
||
{duration:.7, delay:base + .35, easing:EASE_ENTRY_EXP});
|
||
});
|
||
}
|
||
|
||
/* ---------- recipe: four-cards · P19 4 列卡片 ----------
|
||
顶部红线 scaleX 0→1 → 标题入 → 4 卡按 01-04 依次入 */
|
||
function rFourCards(slide, all){
|
||
/* 顶部红线 */
|
||
const topRule = slide.querySelector('[data-anim="line"] > div:first-child');
|
||
if(topRule){
|
||
topRule.style.transformOrigin = 'left center';
|
||
animate(topRule, {opacity:[0,1], scaleX:[0, 1]},
|
||
{duration:.5, delay:.1, easing:EASE_ENTRY_EXP});
|
||
}
|
||
|
||
const head = slide.querySelector('[data-anim="line"]');
|
||
if(head){
|
||
const title = head.querySelector(':scope > div:nth-child(2)');
|
||
if(title) animate(title, {opacity:[0,1], y:[14, 0]},
|
||
{duration:.55, delay:.4, easing:EASE_ENTRY_EXP});
|
||
}
|
||
|
||
const grid = slide.querySelector('[data-anim="up"]');
|
||
if(!grid) return;
|
||
animate(grid, {opacity:[0,1]}, {duration:.3, delay:.55, easing:EASE_PROD});
|
||
|
||
const cards = [...grid.children];
|
||
cards.forEach((card, i)=>{
|
||
animate(card, {opacity:[0,1], y:[18, 0]},
|
||
{duration:.55, delay:.7 + i*.13, easing:EASE_ENTRY_EXP});
|
||
});
|
||
}
|
||
|
||
/* ============ P20 · Stacked KPI Ledger · 4 行账单逐行点亮 + 行间发丝从左画 ============ */
|
||
function rStackedLedger(slide, all){
|
||
const ledger = slide.querySelector('[data-anim="ledger"]');
|
||
if(!ledger) return;
|
||
animate(ledger, {opacity:[0,1]}, {duration:.3, delay:.1, easing:EASE_PROD});
|
||
|
||
const rows = [...ledger.querySelectorAll('.ledger-row')];
|
||
rows.forEach((row, i)=>{
|
||
const base = .25 + i*.18;
|
||
const num = row.querySelector('.ledger-num');
|
||
const label = row.querySelector('.ledger-label');
|
||
const icon = row.querySelector('.ledger-icon');
|
||
if(num) animate(num, {opacity:[0,1], y:[20, 0]}, {duration:.7, delay:base, easing:EASE_ENTRY_EXP});
|
||
if(label) animate(label, {opacity:[0,1], x:[-12, 0]}, {duration:.55, delay:base + .12, easing:EASE_ENTRY_EXP});
|
||
if(icon) animate(icon, {opacity:[0,1], scale:[.6,1]},{duration:.55, delay:base + .22, easing:EASE_ENTRY_EXP});
|
||
});
|
||
}
|
||
|
||
/* ============ P21 · Tech Spec Sheet · 标题分行 / KPI 顶线画出 + count 风感 / 竖线弹起 / 底巨数 ============ */
|
||
function rTechSpec(slide, all){
|
||
const head = slide.querySelector('[data-anim="line"]');
|
||
if(head) fade(head, {duration:.5, y:8});
|
||
|
||
const main = slide.querySelector('[data-anim="up"]');
|
||
if(main){
|
||
animate(main, {opacity:[0,1]}, {duration:.3, delay:.25, easing:EASE_PROD});
|
||
|
||
/* 左大标题分行 */
|
||
const titleLines = main.querySelector(':scope > div:first-child > div:first-child');
|
||
if(titleLines){
|
||
animate(titleLines, {opacity:[0,1], y:[18, 0]}, {duration:.7, delay:.35, easing:EASE_ENTRY_EXP});
|
||
}
|
||
const titleNote = main.querySelector(':scope > div:first-child > div:nth-child(2)');
|
||
if(titleNote){
|
||
animate(titleNote, {opacity:[0,1], y:[10, 0]}, {duration:.5, delay:.95, easing:EASE_ENTRY_EXP});
|
||
}
|
||
|
||
/* 三 KPI · 顶线 scaleX + 数字 fade-up + 副文字 */
|
||
const kpis = [...main.querySelectorAll(':scope > div:not([data-anim]):not(:first-child)')];
|
||
kpis.forEach((kpi, i)=>{
|
||
const base = .55 + i*.18;
|
||
const topRule = kpi.querySelector(':scope > div:first-child');
|
||
if(topRule){
|
||
topRule.style.transformOrigin = 'left center';
|
||
animate(topRule, {scaleX:[0,1], opacity:[0,1]}, {duration:.5, delay:base, easing:EASE_ENTRY_EXP});
|
||
}
|
||
const num = kpi.querySelector('.kpi-num');
|
||
if(num) animate(num, {opacity:[0,1], y:[14, 0]}, {duration:.6, delay:base + .15, easing:EASE_ENTRY_EXP});
|
||
const otherKids = [...kpi.children].filter(el=>el !== topRule && el !== num);
|
||
otherKids.forEach((el, j)=>{
|
||
animate(el, {opacity:[0,1]}, {duration:.4, delay:base + .25 + j*.05, easing:EASE_PROD});
|
||
});
|
||
});
|
||
|
||
}
|
||
|
||
/* 底部 hero 区: 巨数 + goal + tags + 右下竖线 */
|
||
const hero = slide.querySelector('[data-anim="hero"]');
|
||
if(hero){
|
||
animate(hero, {opacity:[0,1]}, {duration:.3, delay:1.3, easing:EASE_PROD});
|
||
const bottomHero = hero.querySelector('.bottom-hero');
|
||
if(bottomHero) animate(bottomHero, {opacity:[0,1], y:[24, 0], scale:[.92, 1]}, {duration:.7, delay:1.4, easing:EASE_ENTRY_EXP});
|
||
const middle = hero.querySelector(':scope > div:nth-child(2)');
|
||
if(middle){
|
||
const kids = [...middle.children];
|
||
kids.forEach((el, i)=>{
|
||
if(el.style && el.style.background === 'var(--ink)'){
|
||
el.style.transformOrigin = 'left center';
|
||
animate(el, {scaleX:[0,1], opacity:[0,1]}, {duration:.5, delay:1.6 + i*.1, easing:EASE_ENTRY_EXP});
|
||
} else {
|
||
animate(el, {opacity:[0,1], y:[10, 0]}, {duration:.5, delay:1.55 + i*.1, easing:EASE_ENTRY_EXP});
|
||
}
|
||
});
|
||
}
|
||
/* 右下: 文字先入, 9 根竖线再从底部 scaleY 弹起 */
|
||
const right = hero.querySelector(':scope > div:nth-child(3)');
|
||
if(right){
|
||
const rightText = right.querySelector(':scope > div:last-child');
|
||
if(rightText) animate(rightText, {opacity:[0,1], y:[10, 0]}, {duration:.5, delay:1.85, easing:EASE_ENTRY_EXP});
|
||
}
|
||
const bars = slide.querySelectorAll('[data-anim="bars"] .vbar');
|
||
bars.forEach((bar, i)=>{
|
||
bar.style.transformOrigin = 'bottom';
|
||
animate(bar, {scaleY:[0,1], opacity:[0,1]}, {duration:.5, delay:2.0 + i*.04, easing:EASE_ENTRY_EXP});
|
||
});
|
||
}
|
||
}
|
||
|
||
/* ============ P22 · Image Hero · 图缓推 + 标题白块从左滑入 + 三 KPI 顶线画出 ============ */
|
||
function rImageHero(slide, all){
|
||
const img = slide.querySelector('[data-anim="img"] img');
|
||
if(img){
|
||
animate(img, {opacity:[0,1], scale:[1.06, 1]}, {duration:1.1, delay:.05, easing:EASE_ENTRY_EXP});
|
||
}
|
||
|
||
const titleBlock = slide.querySelector('[data-anim="title-block"]');
|
||
if(titleBlock){
|
||
titleBlock.style.transformOrigin = 'left center';
|
||
animate(titleBlock, {opacity:[0,1], scaleX:[0, 1]}, {duration:.7, delay:.45, easing:EASE_ENTRY_EXP});
|
||
const titleText = titleBlock.querySelector('div');
|
||
if(titleText) animate(titleText, {opacity:[0,1]}, {duration:.4, delay:.85, easing:EASE_PROD});
|
||
}
|
||
|
||
const kpiWrap = slide.querySelector('[data-anim="kpi"]');
|
||
if(kpiWrap){
|
||
animate(kpiWrap, {opacity:[0,1]}, {duration:.3, delay:.7, easing:EASE_PROD});
|
||
|
||
/* 段落 */
|
||
const para = kpiWrap.querySelector(':scope > div:first-child');
|
||
if(para) animate(para, {opacity:[0,1], y:[14, 0]}, {duration:.6, delay:.85, easing:EASE_ENTRY_EXP});
|
||
|
||
/* 三列 KPI · 顶线 scaleX + 数字升起 */
|
||
const cols = [...kpiWrap.querySelectorAll(':scope > div:nth-child(2) > div')];
|
||
cols.forEach((col, i)=>{
|
||
const base = 1.1 + i*.18;
|
||
const topRule = col.querySelector(':scope > div:first-child');
|
||
if(topRule){
|
||
topRule.style.transformOrigin = 'left center';
|
||
animate(topRule, {scaleX:[0,1], opacity:[0,1]}, {duration:.5, delay:base, easing:EASE_ENTRY_EXP});
|
||
}
|
||
const cat = col.querySelector('.t-meta');
|
||
if(cat) animate(cat, {opacity:[0,1]}, {duration:.4, delay:base + .15, easing:EASE_PROD});
|
||
const hero = col.querySelector('.kpi-hero');
|
||
if(hero) animate(hero, {opacity:[0,1], y:[18, 0]}, {duration:.7, delay:base + .25, easing:EASE_ENTRY_EXP});
|
||
const handled = new Set([topRule, cat, hero]);
|
||
[...col.children]
|
||
.filter(el => !handled.has(el))
|
||
.forEach((el, j)=>{
|
||
animate(el, {opacity:[0,1]}, {duration:.4, delay:base + .45 + j*.05, easing:EASE_PROD});
|
||
});
|
||
});
|
||
}
|
||
}
|
||
|
||
const RECIPES = {
|
||
'hero': rHero,
|
||
'progression': rProgression,
|
||
'statement': rStatement,
|
||
'grid-reveal': rGridReveal,
|
||
'stack-build': rStackBuild,
|
||
'measure-up': rMeasureUp,
|
||
'bar-grow': rBarGrow,
|
||
'duo-mirror': rDuoMirror,
|
||
'split-statement': rSplitStatement,
|
||
'timeline-walk': rTimelineWalk,
|
||
'manifesto': rManifesto,
|
||
'three-forces': rThreeForces,
|
||
'loop-form': rLoopForm,
|
||
'matrix-fill': rMatrixFill,
|
||
'field-notes': rFieldNotes,
|
||
'system-diagram': rSystemDiagram,
|
||
'why-now': rWhyNow,
|
||
'four-cards': rFourCards,
|
||
'stacked-ledger': rStackedLedger,
|
||
'tech-spec': rTechSpec,
|
||
'image-hero': rImageHero,
|
||
};
|
||
|
||
function revealStatic(slide){
|
||
resetAnims(slide);
|
||
document.getAnimations?.().forEach(a=>a.cancel());
|
||
slide.querySelectorAll('[data-anim],.row-fill,.tl-node,.stack-block,.bar-tower,.sub-card,.col,.vrule,.kpi-cell,.card-fill,.card-accent,.card-ink')
|
||
.forEach(el=>{
|
||
el.style.opacity='1';
|
||
el.style.transform='none';
|
||
});
|
||
}
|
||
|
||
function playSlide(i){
|
||
const slide = slides[i];
|
||
if(!slide) return;
|
||
lastIdx = i;
|
||
|
||
if(window.__lowPowerMode){
|
||
revealStatic(slide);
|
||
return;
|
||
}
|
||
|
||
resetAnims(slide);
|
||
|
||
/* 关键:[data-anim] 容器很多时候只是占位标记,真正的几何动画在子元素上.
|
||
默认强制 reveal 所有 [data-anim] 容器, recipe 想做块入场时用 motion 的 {opacity:[0,1]} 会自动覆盖 */
|
||
slide.querySelectorAll('[data-anim]').forEach(el=>{
|
||
el.style.opacity = '1';
|
||
el.style.transform = 'none';
|
||
});
|
||
|
||
const all = [...slide.querySelectorAll('[data-anim]')];
|
||
const recipe = slide.dataset.animate;
|
||
const fn = RECIPES[recipe];
|
||
if(fn){ fn(slide, all); return; }
|
||
|
||
/* fallback: 平凡 fade */
|
||
if(all.length) animate(all, {opacity:[0,1], y:[12,0]},
|
||
{duration:.6, delay:i=>i*.08, easing:EASE_ENTRY_EXP});
|
||
}
|
||
|
||
window.__playSlide = playSlide;
|
||
window.__pipeAdvance = ()=>false; /* 当前 deck 不用 pipeline recipe */
|
||
|
||
playSlide(window.__currentSlideIndex || 0);
|
||
}
|
||
</script>
|
||
|
||
<script>
|
||
/* ============== ASCII 点阵呼吸场 · IKB 封面/封底专用 ==============
|
||
sin/cos 二维噪声场驱动字符显隐,营造工业仪表板的"涌动呼吸"质感.
|
||
纯 canvas 2D, mix-blend-mode:screen 让字符在 IKB 底色上自然发亮.
|
||
用法:在需要呼吸场的容器(.canvas-card 或 split .half.b-accent)内首位插入
|
||
<canvas class="ascii-bg" aria-hidden="true">,本脚本会自动扫描并启动. */
|
||
(function(){
|
||
const canvases = [...document.querySelectorAll('canvas.ascii-bg')];
|
||
if(!canvases.length) return;
|
||
|
||
const PALETTE = ' ...:::---+++***◦◦••▢▣';
|
||
const CELL = 16;
|
||
const FONT_SIZE = 13;
|
||
|
||
function setup(c){
|
||
const dpr = Math.min(window.devicePixelRatio || 1, 2);
|
||
const rect = c.getBoundingClientRect();
|
||
if(rect.width < 4 || rect.height < 4) return false;
|
||
c.width = Math.round(rect.width * dpr);
|
||
c.height = Math.round(rect.height * dpr);
|
||
c.__dpr = dpr;
|
||
c.__w = rect.width;
|
||
c.__h = rect.height;
|
||
const ctx = c.getContext('2d');
|
||
ctx.setTransform(dpr,0,0,dpr,0,0);
|
||
const mono = (getComputedStyle(document.documentElement).getPropertyValue('--mono') || 'JetBrains Mono, monospace').trim();
|
||
ctx.font = `500 ${FONT_SIZE}px ${mono}`;
|
||
ctx.textBaseline = 'top';
|
||
c.__ctx = ctx;
|
||
return true;
|
||
}
|
||
|
||
function draw(c, t){
|
||
if(!c.__ctx) return;
|
||
const ctx = c.__ctx, w = c.__w, h = c.__h;
|
||
ctx.clearRect(0, 0, w, h);
|
||
const cols = Math.ceil(w / CELL);
|
||
const rows = Math.ceil(h / CELL);
|
||
for(let r=0; r<rows; r++){
|
||
for(let cc=0; cc<cols; cc++){
|
||
const n = (
|
||
Math.sin(cc * 0.18 + t) +
|
||
Math.sin(r * 0.24 - t * 0.7) +
|
||
Math.sin((cc + r) * 0.12 + t * 0.45) +
|
||
Math.sin(Math.hypot(cc - cols * 0.5, r - rows * 0.5) * 0.16 - t * 0.55)
|
||
) / 4; // [-1, 1]
|
||
const v = (n + 1) / 2; // [0, 1]
|
||
if(v < 0.22) continue;
|
||
const idx = Math.min(PALETTE.length - 1, Math.floor(v * PALETTE.length));
|
||
const ch = PALETTE[idx];
|
||
if(ch === ' ') continue;
|
||
const alpha = 0.08 + (v - 0.22) * 0.55;
|
||
ctx.fillStyle = `rgba(255,255,255,${alpha.toFixed(3)})`;
|
||
ctx.fillText(ch, cc * CELL, r * CELL);
|
||
}
|
||
}
|
||
}
|
||
|
||
function resizeAll(){ canvases.forEach(setup); }
|
||
let pending = null;
|
||
window.addEventListener('resize', ()=>{
|
||
if(window.__lowPowerMode) return;
|
||
if(pending) cancelAnimationFrame(pending);
|
||
pending = requestAnimationFrame(resizeAll);
|
||
}, {passive:true});
|
||
|
||
let t0 = performance.now();
|
||
let frame = 0, asciiRAF = 0, running = false;
|
||
function tick(now){
|
||
if(!running || window.__lowPowerMode){running=false;asciiRAF=0;return;}
|
||
const t = (now - t0) / 1000 * 0.55;
|
||
frame++;
|
||
canvases.forEach(c=>{
|
||
// 离屏 slide 降帧:每 4 帧渲染一次,在屏 slide 每帧渲染
|
||
const slide = c.closest('.slide');
|
||
const rect = slide ? slide.getBoundingClientRect() : null;
|
||
const onscreen = rect && rect.right > 0 && rect.left < window.innerWidth;
|
||
if(!onscreen && (frame & 3) !== 0) return;
|
||
draw(c, t);
|
||
});
|
||
asciiRAF = requestAnimationFrame(tick);
|
||
}
|
||
function start(){
|
||
if(running || window.__lowPowerMode) return;
|
||
resizeAll();
|
||
t0 = performance.now();
|
||
frame = 0;
|
||
running = true;
|
||
asciiRAF = requestAnimationFrame(tick);
|
||
}
|
||
function stop(){
|
||
running = false;
|
||
if(asciiRAF) cancelAnimationFrame(asciiRAF);
|
||
if(pending) cancelAnimationFrame(pending);
|
||
asciiRAF = 0;
|
||
pending = null;
|
||
canvases.forEach(c=>{
|
||
if(c.__ctx) c.__ctx.clearRect(0,0,c.__w || 0,c.__h || 0);
|
||
});
|
||
}
|
||
addEventListener('swiss-low-power-change', e=>{e.detail.on ? stop() : start();});
|
||
start();
|
||
})();
|
||
</script>
|
||
</body>
|
||
</html>
|