1
0

template.html 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858
  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>[必填] 替换为 PPT 标题 · Deck Title</title>
  7. <link rel="preconnect" href="https://fonts.googleapis.com">
  8. <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  9. <link href="https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,500;0,600;0,700;0,800;0,900;1,400;1,700&family=Source+Serif+4:ital,opsz,wght@0,8..60,300;0,8..60,400;0,8..60,500;0,8..60,600;1,8..60,400&family=IBM+Plex+Mono:wght@300;400;500;600&family=Noto+Serif+SC:wght@300;400;500;600;700;900&family=Noto+Sans+SC:wght@300;400;500;700;900&display=swap" rel="stylesheet">
  10. <style>
  11. :root{
  12. /* ============ 主题色(默认:🖋 墨水经典) ============
  13. 切换主题:从 references/themes.md 复制对应的 :root 块
  14. 整体替换这几行(--ink / --ink-rgb / --paper / --paper-rgb)
  15. 其他地方散落的 rgba() 都走 var(--ink-rgb) / var(--paper-rgb),无需逐处改 */
  16. --ink:#0a0a0b;
  17. --ink-rgb:10,10,11;
  18. --paper:#f1efea;
  19. --paper-rgb:241,239,234;
  20. --paper-tint:#e8e5de;
  21. --ink-tint:#18181a;
  22. /* ============ 字体(跨主题固定) ============ */
  23. --mono:"IBM Plex Mono",ui-monospace,monospace;
  24. --serif-en:"Playfair Display","Source Serif 4",Georgia,serif;
  25. --serif-body-en:"Source Serif 4",Georgia,serif;
  26. --serif-zh:"Noto Serif SC",source-han-serif-sc,serif;
  27. --sans-zh:"Noto Sans SC",source-han-sans-sc,sans-serif;
  28. }
  29. *{box-sizing:border-box;margin:0;padding:0}
  30. html,body{width:100%;height:100%;overflow:hidden;background:var(--ink);color:var(--paper);font-family:var(--sans-zh);-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}
  31. /* ============ WebGL 双背景 ============ */
  32. canvas.bg{position:fixed;inset:0;width:100vw;height:100vh;z-index:0;display:block;transition:opacity 1.2s ease}
  33. canvas#bg-light{opacity:0}
  34. canvas#bg-dark{opacity:1}
  35. body.light-bg canvas#bg-light{opacity:1}
  36. body.light-bg canvas#bg-dark{opacity:0}
  37. body.low-power canvas.bg{display:none!important}
  38. /* ============ Deck 容器 + 翻页 ============ */
  39. /* width: NSLIDES * 100vw,会在 JS 里动态矫正 */
  40. #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}
  41. .slide{width:100vw;height:100vh;flex:0 0 100vw;position:relative;padding:6vh 6vw 10vh 6vw;display:flex;flex-direction:column;overflow:hidden}
  42. .slide.light{color:var(--ink);background:var(--paper)}
  43. .slide.dark{color:var(--paper);background:var(--ink)}
  44. /* 默认页:遮罩较厚,保证文字可读 */
  45. .slide::before{content:"";position:absolute;inset:0;z-index:-1;pointer-events:none;transition:background .7s ease}
  46. .slide.light::before{background:rgba(var(--paper-rgb),.78);backdrop-filter:blur(3px)}
  47. .slide.dark::before{background:rgba(var(--ink-rgb),.78);backdrop-filter:blur(3px)}
  48. /* Hero 页:遮罩大幅降低,让 WebGL 背景明显透出 */
  49. .slide.hero.light::before{background:rgba(var(--paper-rgb),.16);backdrop-filter:none}
  50. .slide.hero.dark::before{background:rgba(var(--ink-rgb),.12);backdrop-filter:none}
  51. /* Hero 页顶底微弱渐隐,保证 chrome/foot 区域可读 */
  52. .slide.hero::after{content:"";position:absolute;inset:0;z-index:-1;pointer-events:none}
  53. .slide.hero.light::after{background:linear-gradient(180deg,rgba(var(--paper-rgb),.28) 0%,rgba(var(--paper-rgb),0) 14%,rgba(var(--paper-rgb),0) 86%,rgba(var(--paper-rgb),.28) 100%)}
  54. .slide.hero.dark::after{background:linear-gradient(180deg,rgba(var(--ink-rgb),.32) 0%,rgba(var(--ink-rgb),0) 14%,rgba(var(--ink-rgb),0) 86%,rgba(var(--ink-rgb),.32) 100%)}
  55. /* ============ Magazine chrome:顶部 meta + 底部 foot ============ */
  56. .chrome{display:flex;justify-content:space-between;align-items:flex-start;font-family:var(--mono);font-size:12px;letter-spacing:.18em;text-transform:uppercase;opacity:.7}
  57. .chrome .left,.chrome .right{display:flex;gap:2.4em;align-items:center}
  58. .chrome .sep{width:40px;height:1px;background:currentColor;opacity:.4}
  59. .foot{margin-top:auto;display:flex;justify-content:space-between;align-items:flex-end;font-family:var(--mono);font-size:12px;letter-spacing:.14em;text-transform:uppercase;opacity:.55}
  60. .foot .title{font-family:var(--serif-zh);font-weight:400;letter-spacing:.05em;text-transform:none;opacity:.75;font-size:13px}
  61. .tag{display:inline-block;font-family:var(--mono);font-size:11px;letter-spacing:.24em;text-transform:uppercase;padding:6px 14px;border:1px solid currentColor;opacity:.85}
  62. .rule{width:100%;height:1px;background:currentColor;opacity:.25;margin:3vh 0}
  63. .rule.v{width:1px;height:100%;margin:0}
  64. /* ============ 字体规则 ============
  65. · 衬线(Noto Serif SC / Playfair):大标题、重点金句、数字
  66. · 非衬线(Noto Sans SC):正文描述、body、补充说明
  67. · 等宽(IBM Plex Mono):kicker、meta 小标签、foot 右侧
  68. */
  69. .kicker{font-family:var(--mono);font-size:12px;letter-spacing:.3em;text-transform:uppercase;opacity:.6;margin-bottom:2.6vh}
  70. .display{font-family:var(--serif-en);font-weight:700;font-size:11vw;line-height:.92;letter-spacing:-.025em}
  71. .display-zh{font-family:var(--serif-zh);font-weight:700;font-size:7.8vw;line-height:1.04;letter-spacing:-.005em}
  72. .h1-zh{font-family:var(--serif-zh);font-weight:700;font-size:4.6vw;line-height:1.12;letter-spacing:-.005em}
  73. .h2-zh{font-family:var(--serif-zh);font-weight:600;font-size:3.2vw;line-height:1.2;letter-spacing:0}
  74. .h3-zh{font-family:var(--serif-zh);font-weight:500;font-size:1.9vw;line-height:1.35}
  75. .body-zh{font-family:var(--sans-zh);font-weight:400;font-size:max(15px,1.22vw);line-height:1.75;opacity:.82;letter-spacing:.01em}
  76. .body-serif{font-family:var(--serif-zh);font-weight:400;font-size:max(15px,1.3vw);line-height:1.65;opacity:.88}
  77. .lead{font-family:var(--serif-zh);font-weight:400;font-size:1.9vw;line-height:1.4;opacity:.85}
  78. .meta{font-family:var(--mono);font-size:max(11px,.88vw);letter-spacing:.16em;text-transform:uppercase;opacity:.6}
  79. .big-num{font-family:var(--serif-en);font-weight:800;font-size:10vw;line-height:.85;letter-spacing:-.03em;font-feature-settings:"tnum"}
  80. .mid-num{font-family:var(--serif-en);font-weight:700;font-size:5.5vw;line-height:.88;letter-spacing:-.02em;font-feature-settings:"tnum"}
  81. .ghost{font-family:var(--serif-en);font-weight:900;font-size:34vw;line-height:.8;opacity:.06;letter-spacing:-.04em;position:absolute;font-feature-settings:"tnum"}
  82. em{font-style:italic;font-family:var(--serif-en)}
  83. .en{font-family:var(--serif-en);font-style:italic;font-weight:500}
  84. /* ============ 布局工具 ============ */
  85. .col{display:flex;flex-direction:column;gap:2.4vh}
  86. .row{display:flex;align-items:center;gap:3vw}
  87. .grid-6{display:grid;grid-template-columns:repeat(3,1fr);grid-template-rows:repeat(2,1fr);gap:4vw 6vw;flex:1;align-content:center;padding:2vh 0}
  88. .grid-9{display:grid;grid-template-columns:repeat(3,1fr);grid-template-rows:repeat(3,1fr);gap:3vh 4vw;flex:1;align-content:center}
  89. .grid-4{display:grid;grid-template-columns:repeat(2,1fr);grid-template-rows:repeat(2,1fr);gap:4vh 6vw;flex:1;align-content:center}
  90. .grid-3{display:grid;grid-template-columns:repeat(3,1fr);gap:4vw;flex:1;align-content:center}
  91. .split{display:grid;grid-template-columns:1fr 1fr;gap:4vw;flex:1;align-items:center}
  92. .split-55{display:grid;grid-template-columns:55fr 45fr;gap:5vw;flex:1;align-items:stretch}
  93. .fill{flex:1}
  94. .center{align-items:center;justify-content:center;text-align:center}
  95. .bottom-left{position:absolute;left:6vw;bottom:9vh;max-width:50vw}
  96. .bottom-right{position:absolute;right:6vw;bottom:9vh;max-width:50vw;text-align:right}
  97. .top-right{position:absolute;right:6vw;top:6vh;text-align:right}
  98. /* ============ Stat(数字矩阵) ============ */
  99. .stat{display:flex;flex-direction:column;gap:1vh;align-items:flex-start}
  100. .stat .n{font-family:var(--serif-en);font-weight:800;font-size:8vw;line-height:.88;letter-spacing:-.03em;font-feature-settings:"tnum"}
  101. .stat .l{font-family:var(--sans-zh);font-size:max(13px,1.05vw);opacity:.7;margin-top:1vh;font-weight:400;line-height:1.5}
  102. .stat .m{font-family:var(--mono);font-size:10px;letter-spacing:.22em;text-transform:uppercase;opacity:.5;margin-bottom:.2vh}
  103. /* ============ Callout(引用框) ============ */
  104. .callout{padding:3vh 2.4vw;border-left:3px solid currentColor;position:relative;font-family:var(--serif-zh);font-size:max(15px,1.2vw);line-height:1.55;opacity:.92}
  105. .slide.light .callout{background:rgba(var(--ink-rgb),.05)}
  106. .slide.dark .callout{background:rgba(var(--paper-rgb),.06)}
  107. .callout .cite{display:block;margin-top:1.6vh;font-family:var(--mono);font-size:11px;letter-spacing:.2em;text-transform:uppercase;opacity:.6}
  108. .callout .q-big{font-family:var(--serif-zh);font-weight:600;font-size:max(17px,1.6vw);line-height:1.42}
  109. /* ============ Platform(平台卡) ============ */
  110. .plat{display:flex;flex-direction:column;justify-content:flex-end;padding:2vh 0;border-top:1px solid currentColor;border-color:rgba(127,127,127,.35)}
  111. .plat .name{font-family:var(--serif-zh);font-weight:700;font-size:1.8vw;margin-bottom:.6vh}
  112. .plat .nb{font-family:var(--serif-en);font-weight:700;font-size:3.2vw;letter-spacing:-.02em;line-height:1;font-feature-settings:"tnum"}
  113. .plat .sub{font-family:var(--mono);font-size:10px;letter-spacing:.18em;text-transform:uppercase;opacity:.55;margin-top:.6vh}
  114. .plat .fill{font-family:var(--sans-zh);font-weight:300;font-size:2.4vw;opacity:.28;letter-spacing:-.01em;line-height:1}
  115. /* ============ Rowline(表格行) ============ */
  116. .rowline{display:grid;grid-template-columns:1fr 2fr 1fr;gap:2vw;padding:2.2vh 0;border-top:1px solid currentColor;align-items:center;border-color:rgba(127,127,127,.25)}
  117. .rowline:last-child{border-bottom:1px solid currentColor;border-color:rgba(127,127,127,.25)}
  118. .rowline .k{font-family:var(--serif-zh);font-weight:700;font-size:1.7vw}
  119. .rowline .v{font-family:var(--sans-zh);font-weight:400;font-size:max(14px,1.2vw);opacity:.85;line-height:1.55}
  120. .rowline .m{font-family:var(--mono);font-size:11px;letter-spacing:.2em;text-transform:uppercase;opacity:.6;justify-self:end}
  121. /* ============ Pillar(支柱卡片) ============ */
  122. .pillar{display:flex;flex-direction:column;gap:1.8vh}
  123. .pillar .ic{font-family:var(--serif-en);font-style:italic;font-size:2.6vw;opacity:.45;font-weight:400}
  124. .pillar .ic svg{width:2.8vw;height:2.8vw;stroke-width:1.2;opacity:.7}
  125. .pillar .t{font-family:var(--serif-zh);font-weight:700;font-size:2.4vw;line-height:1.1}
  126. .pillar .d{font-family:var(--sans-zh);font-weight:400;font-size:max(14px,1.1vw);opacity:.76;line-height:1.6}
  127. /* ============ Signature / Highlight ============ */
  128. .sign{font-family:var(--serif-en);font-style:italic;font-weight:500;font-size:2vw;opacity:.7}
  129. .hi{position:relative;display:inline}
  130. .slide.dark .hi::after{content:"";position:absolute;left:-.1em;right:-.1em;bottom:-.05em;height:.28em;background:rgba(var(--paper-rgb),.15);z-index:-1}
  131. .slide.light .hi::after{content:"";position:absolute;left:-.1em;right:-.1em;bottom:-.05em;height:.28em;background:rgba(var(--ink-rgb),.08);z-index:-1}
  132. /* ============ Icons(Lucide via CDN) ============ */
  133. .ico{width:1em;height:1em;display:inline-block;vertical-align:-.12em;stroke:currentColor;fill:none;stroke-width:1.4;stroke-linecap:round;stroke-linejoin:round;flex-shrink:0}
  134. .ico-lg,.ico-md,.ico-sm{fill:none;stroke:currentColor;stroke-linecap:round;stroke-linejoin:round}
  135. .ico-lg{width:2.6vw;height:2.6vw;stroke-width:1.2;display:inline-block}
  136. .ico-md{width:1.8vw;height:1.8vw;stroke-width:1.3;display:inline-block;vertical-align:-.4em}
  137. .ico-sm{width:1.1vw;height:1.1vw;stroke-width:1.4;display:inline-block;vertical-align:-.15em;opacity:.7}
  138. /* ============ 图片占位(虚线框,提示设计师位置) ============ */
  139. .img-slot{border:1.5px dashed rgba(127,127,127,.4);display:flex;align-items:center;justify-content:center;flex-direction:column;gap:1vh;padding:2vh 2vw;font-family:var(--mono);font-size:10px;letter-spacing:.28em;text-transform:uppercase;opacity:.55;position:relative;aspect-ratio:16/9;width:100%;max-height:56vh;margin-inline:auto;box-sizing:border-box}
  140. .img-slot::before{content:"";position:absolute;inset:8px;border:1px solid currentColor;opacity:.2}
  141. .img-slot .plus{font-size:2vw;font-weight:300;opacity:.5;letter-spacing:0}
  142. .img-slot .label{position:relative;z-index:2;text-align:center}
  143. .img-slot.r-4x3{aspect-ratio:4/3}
  144. .img-slot.r-3x2{aspect-ratio:3/2}
  145. .img-slot.r-1x1{aspect-ratio:1/1}
  146. /* ============ 图片实填框(关键:固定高度 + 只裁底部) ============
  147. 重要约束:高度用内联 height:Nvh 精确控制,不要用 aspect-ratio(会撑破布局)
  148. object-position:top center 保证严禁裁剪顶部和左右,只裁剪底部
  149. */
  150. .frame-img{overflow:hidden;position:relative;background:rgba(0,0,0,.04);box-sizing:border-box;width:100%;border-radius:4px}
  151. .slide.dark .frame-img{background:rgba(255,255,255,.04);border-color:rgba(255,255,255,.12)}
  152. .frame-img > img{width:100%;height:100%;object-fit:cover;object-position:top center;display:block}
  153. .frame-img.fit-contain > img{object-fit:contain;object-position:center center}
  154. .frame-img.r-16x9{aspect-ratio:16/9;max-height:64vh}
  155. .frame-img.r-16x10{aspect-ratio:16/10;max-height:56vh}
  156. .frame-img.r-4x3{aspect-ratio:4/3;max-height:56vh}
  157. .frame-img.r-3x2{aspect-ratio:3/2;max-height:46vh}
  158. .frame-img.r-3x4{aspect-ratio:3/4;max-height:60vh}
  159. .frame-img.r-1x1{aspect-ratio:1/1;max-height:50vh}
  160. .frame-img.h-16{height:16vh}
  161. .frame-img.h-18{height:18vh}
  162. .frame-img.h-22{height:22vh}
  163. .frame-img.h-26{height:26vh}
  164. .frame-img.h-28{height:28vh}
  165. .frame-cap{display:flex;justify-content:space-between;align-items:baseline;gap:1vw;margin-top:.8vh;font-family:var(--mono);font-size:10px;letter-spacing:.22em;text-transform:uppercase;opacity:.72}
  166. .frame-cap .pf{font-family:var(--serif-zh);font-weight:600;font-size:max(13px,1vw);letter-spacing:.04em;text-transform:none;opacity:.94}
  167. .frame-cap .nb{font-family:var(--serif-en);font-style:italic;font-size:max(15px,1.2vw);letter-spacing:.02em;text-transform:none;opacity:.88}
  168. .frame-cap .idx{font-family:var(--mono);opacity:.5}
  169. figure.tile{display:flex;flex-direction:column;margin:0;min-width:0}
  170. figure.tile > .frame-img{flex:0 0 auto}
  171. /* ============ 导航 ============ */
  172. #nav{position:fixed;left:50%;bottom:2.6vh;transform:translateX(-50%);z-index:30;display:flex;gap:10px;padding:8px 14px;border-radius:999px;background:rgba(0,0,0,.18);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px)}
  173. #nav .dot{width:8px;height:8px;border-radius:50%;background:rgba(255,255,255,.3);cursor:pointer;transition:all .3s ease;border:0;padding:0}
  174. #nav .dot:hover{background:rgba(255,255,255,.5);transform:scale(1.15)}
  175. #nav .dot.active{background:rgba(255,255,255,.95);width:22px;border-radius:999px}
  176. body.light-bg #nav{background:rgba(255,255,255,.25)}
  177. body.light-bg #nav .dot{background:rgba(var(--ink-rgb),.25)}
  178. body.light-bg #nav .dot.active{background:rgba(var(--ink-rgb),.9)}
  179. #hint{position:fixed;bottom:3vh;right:3vw;z-index:30;font-family:var(--mono);font-size:10px;letter-spacing:.2em;text-transform:uppercase;opacity:.4;mix-blend-mode:difference;color:#aaa}
  180. body.low-power #hint{opacity:.72;color:var(--paper);mix-blend-mode:normal}
  181. body.light-bg.low-power #hint{color:var(--ink)}
  182. /* ============================================================
  183. ============ LAYOUTS API · 面向 agent 的类(v2)============
  184. 所有 layouts.md 中的骨架都基于下面这套命名。
  185. 如果你在 layouts.md 里看到某个类,它必须在下面有定义。
  186. ============================================================ */
  187. /* ---------- .frame:每页主内容容器 ---------- */
  188. .frame{flex:1;display:flex;flex-direction:column;min-height:0;overflow:hidden}
  189. /* 当 .frame 同时加了 grid 类时,grid 的 display:grid 覆盖 flex */
  190. .frame.grid-2-7-5,
  191. .frame.grid-2-6-6,
  192. .frame.grid-2-8-4,
  193. .frame.grid-3-3,
  194. .frame.grid-6{display:grid}
  195. /* ---------- 标题层级(API 名称,衬线为主) ---------- */
  196. .h-hero{
  197. font-family:var(--serif-zh);
  198. font-weight:900;
  199. font-size:10vw;
  200. line-height:.96;
  201. letter-spacing:-.02em;
  202. }
  203. .h-xl{
  204. font-family:var(--serif-zh);
  205. font-weight:700;
  206. font-size:6.2vw;
  207. line-height:1.08;
  208. letter-spacing:-.01em;
  209. }
  210. .h-sub{
  211. font-family:var(--serif-zh);
  212. font-weight:500;
  213. font-size:3.1vw;
  214. line-height:1.25;
  215. letter-spacing:0;
  216. opacity:.7;
  217. }
  218. .h-md{
  219. font-family:var(--serif-zh);
  220. font-weight:600;
  221. font-size:2.3vw;
  222. line-height:1.3;
  223. }
  224. /* 英文标题专用(Playfair 衬线) */
  225. .h-hero-en,.h-xl-en{font-family:var(--serif-en);letter-spacing:-.025em}
  226. /* ---------- lead 引语 ---------- */
  227. .lead{
  228. font-family:var(--serif-zh);
  229. font-weight:400;
  230. font-size:1.75vw;
  231. line-height:1.5;
  232. opacity:.86;
  233. }
  234. /* ---------- meta-row 底部元数据 ---------- */
  235. .meta-row{
  236. display:flex;
  237. gap:1.2em;
  238. align-items:baseline;
  239. flex-wrap:wrap;
  240. font-family:var(--mono);
  241. font-size:max(12px,.92vw);
  242. letter-spacing:.16em;
  243. text-transform:uppercase;
  244. opacity:.6;
  245. }
  246. /* ---------- stat-card(数据大字报用) ---------- */
  247. .stat-card{
  248. display:flex;
  249. flex-direction:column;
  250. gap:.8vh;
  251. align-items:flex-start;
  252. padding-top:1.6vh;
  253. border-top:1px solid currentColor;
  254. border-color:rgba(127,127,127,.3);
  255. }
  256. .stat-card .stat-label{
  257. font-family:var(--mono);
  258. font-size:max(10px,.78vw);
  259. letter-spacing:.24em;
  260. text-transform:uppercase;
  261. opacity:.55;
  262. }
  263. .stat-card .stat-nb{
  264. font-family:var(--serif-en);
  265. font-weight:800;
  266. font-size:5.8vw;
  267. line-height:.9;
  268. letter-spacing:-.03em;
  269. font-feature-settings:"tnum";
  270. margin-top:.4vh;
  271. }
  272. .stat-card .stat-nb .stat-unit{
  273. font-family:var(--serif-zh);
  274. font-weight:500;
  275. font-size:.38em;
  276. letter-spacing:0;
  277. opacity:.72;
  278. margin-left:.14em;
  279. }
  280. .stat-card .stat-note{
  281. font-family:var(--sans-zh);
  282. font-weight:400;
  283. font-size:max(13px,1.05vw);
  284. line-height:1.5;
  285. opacity:.72;
  286. margin-top:.6vh;
  287. }
  288. /* 当 stat-card 用于 grid-4(2x2),数字适度放大;若页面有标题+引文在上方,可内联 style 覆盖为更小值 */
  289. .grid-4 .stat-card .stat-nb{font-size:5vw}
  290. /* 当只有 3 个,字也可以稍大 */
  291. .grid-3 .stat-card .stat-nb{font-size:6.8vw}
  292. /* ---------- pipeline(流水线) ---------- */
  293. .pipeline-section{
  294. margin-top:4.4vh;
  295. padding-top:2.8vh;
  296. border-top:1px dashed rgba(127,127,127,.32);
  297. }
  298. .pipeline-section:first-of-type{
  299. border-top:0;
  300. padding-top:0;
  301. margin-top:3vh;
  302. }
  303. .pipeline-label{
  304. font-family:var(--mono);
  305. font-size:max(11px,.85vw);
  306. letter-spacing:.24em;
  307. text-transform:uppercase;
  308. opacity:.62;
  309. margin-bottom:2.2vh;
  310. }
  311. .pipeline{
  312. display:grid;
  313. grid-template-columns:repeat(5,1fr);
  314. gap:1.2vw;
  315. }
  316. .pipeline[data-cols="3"]{grid-template-columns:repeat(3,1fr)}
  317. .pipeline[data-cols="4"]{grid-template-columns:repeat(4,1fr)}
  318. .pipeline[data-cols="6"]{grid-template-columns:repeat(6,1fr)}
  319. .step{
  320. display:flex;
  321. flex-direction:column;
  322. gap:.8vh;
  323. padding-top:1.4vh;
  324. border-top:1px solid currentColor;
  325. border-color:rgba(127,127,127,.35);
  326. }
  327. .step-nb{
  328. font-family:var(--serif-en);
  329. font-style:italic;
  330. font-weight:500;
  331. font-size:1.15vw;
  332. opacity:.45;
  333. }
  334. .step-title{
  335. font-family:var(--sans-zh);
  336. font-weight:700;
  337. font-size:1.55vw;
  338. letter-spacing:.01em;
  339. line-height:1.2;
  340. }
  341. .step-desc{
  342. font-family:var(--sans-zh);
  343. font-weight:400;
  344. font-size:max(12px,.95vw);
  345. line-height:1.45;
  346. opacity:.72;
  347. }
  348. /* ---------- 网格(layouts.md 所用) ---------- */
  349. /* 这些类独立挂到任何容器上都能生效,不依赖 .frame 复合选择器 */
  350. .grid-2-7-5{display:grid;grid-template-columns:7fr 5fr;gap:3vw 4vh;align-items:start}
  351. .grid-2-6-6{display:grid;grid-template-columns:1fr 1fr;gap:3vw 4vh;align-items:start}
  352. .grid-2-8-4{display:grid;grid-template-columns:8fr 4fr;gap:3vw 4vh;align-items:start}
  353. .grid-3-3{
  354. display:grid;
  355. grid-template-columns:repeat(3,1fr);
  356. grid-auto-rows:minmax(0,1fr);
  357. gap:2.4vh 2vw;
  358. }
  359. /* grid-6 已在旧样式里定义为 3x2,这里仅补 align */
  360. /* ---------- 图片 frame-img(layouts.md 主命名) ---------- */
  361. /* 在旧样式里已定义,这里补 img-cap 命名别名与增强 */
  362. figure.frame-img{margin:0;display:flex;flex-direction:column;min-width:0}
  363. .img-cap{
  364. display:block;
  365. margin-top:.8vh;
  366. font-family:var(--mono);
  367. font-size:max(10px,.8vw);
  368. letter-spacing:.22em;
  369. text-transform:uppercase;
  370. opacity:.6;
  371. }
  372. /* callout src 命名别名 */
  373. .callout-src{
  374. display:block;
  375. margin-top:1.6vh;
  376. font-family:var(--mono);
  377. font-size:11px;
  378. letter-spacing:.2em;
  379. text-transform:uppercase;
  380. opacity:.6;
  381. }
  382. /* ---------- chrome & foot 补位(layouts.md 简单写法) ---------- */
  383. .chrome{font-family:var(--mono);font-size:max(11px,.78vw);letter-spacing:.2em;text-transform:uppercase;opacity:.62}
  384. .foot{font-family:var(--mono);font-size:max(11px,.78vw);letter-spacing:.18em;text-transform:uppercase;opacity:.5}
  385. /* ============ 动效系统(Motion One 驱动) ============
  386. 所有 [data-anim] 元素默认隐藏,进入页面时由 JS 逐个揭示。
  387. - cascade(默认):按 DOM 顺序 stagger 120ms 淡入 + y:16→0
  388. - hero(hero 页自动):慢一点的 stagger 180ms
  389. - quote(含 [data-anim="line"]):逐行 600ms 淡入
  390. - directional(含 [data-anim="left|right"]):左→分隔线→右
  391. - pipeline(data-animate="pipeline"):Space/→ 手动推进
  392. Motion One 没加载成功时(CDN 断 + 本地丢),所有 [data-anim] 会兜底显示。
  393. */
  394. [data-anim]{opacity:1}
  395. body.motion-ready [data-anim]{opacity:0}
  396. body.motion-ready [data-anim="left"]{transform:translateX(-24px)}
  397. body.motion-ready [data-anim="right"]{transform:translateX(24px)}
  398. body.motion-ready [data-anim="line"]{opacity:0;transform:translateY(10px)}
  399. body.motion-ready [data-animate="pipeline"] [data-anim]{opacity:.15}
  400. body.low-power #deck{transition:none!important}
  401. body.low-power *,
  402. body.low-power *::before,
  403. body.low-power *::after{animation:none!important;transition:none!important}
  404. body.low-power.motion-ready [data-anim],
  405. body.low-power [data-anim]{opacity:1!important;transform:none!important}
  406. /* ---------- 响应式降级 ---------- */
  407. @media (max-width:900px){
  408. .display{font-size:16vw}
  409. .display-zh{font-size:12vw}
  410. .h1-zh{font-size:7vw}
  411. .h-hero{font-size:14vw}
  412. .h-xl{font-size:9vw}
  413. .pipeline{grid-template-columns:repeat(2,1fr)}
  414. .grid-2-7-5,.grid-2-6-6,.grid-2-8-4{grid-template-columns:1fr}
  415. }
  416. </style>
  417. </head>
  418. <body>
  419. <script>
  420. (function(){
  421. const KEY = 'guizang-ppt-low-power';
  422. const reduced = matchMedia('(prefers-reduced-motion: reduce)').matches;
  423. const stored = localStorage.getItem(KEY);
  424. window.__lowPowerMode = stored === '1' || (stored === null && reduced);
  425. function updateHint(){
  426. const hint = document.getElementById('hint');
  427. if(hint) hint.textContent = `← → 翻页 · B ${window.__lowPowerMode ? '动态' : '静态'} · ESC 索引`;
  428. }
  429. window.__setLowPowerMode = function(on, opts={}){
  430. window.__lowPowerMode = !!on;
  431. document.body.classList.toggle('low-power', window.__lowPowerMode);
  432. if(opts.persist !== false) localStorage.setItem(KEY, window.__lowPowerMode ? '1' : '0');
  433. if(window.__lowPowerMode && document.getAnimations){
  434. document.getAnimations().forEach(a=>a.cancel());
  435. }
  436. updateHint();
  437. dispatchEvent(new CustomEvent('ppt-low-power-change', {detail:{on:window.__lowPowerMode}}));
  438. if(window.__playSlide) window.__playSlide(window.__currentSlideIndex || 0);
  439. };
  440. document.body.classList.toggle('low-power', window.__lowPowerMode);
  441. addEventListener('DOMContentLoaded', updateHint, {once:true});
  442. })();
  443. </script>
  444. <canvas id="bg-dark" class="bg"></canvas>
  445. <canvas id="bg-light" class="bg"></canvas>
  446. <div id="hint">← → 翻页 · B 静态 · ESC 索引</div>
  447. <div id="deck">
  448. <!-- ============================================================
  449. SLIDES 插入区 · 在此处填充所有 <section class="slide ..."> 页面
  450. 每页模板参考 references/page-patterns.md
  451. 页面组件参考 references/components.md
  452. ============================================================ -->
  453. <!-- SLIDES_HERE -->
  454. </div>
  455. <div id="nav"></div>
  456. <script>
  457. /* =============== WebGL 双背景 ===============
  458. 深色页:Holographic Dispersion(全息色散 · 钛金暗流)—— 彩虹微扰、鼠标径向涟漪
  459. 浅色页:Spiral Vortex(旋转涡流 · 银色珍珠)—— domain-warp 流动、无中心
  460. 修改风格请参考 references/webgl-backgrounds.md
  461. */
  462. const VS = `attribute vec2 position;void main(){gl_Position=vec4(position,0.0,1.0);}`;
  463. const FS_DARK = `precision highp float;
  464. uniform vec2 u_resolution;uniform float u_time;uniform vec2 u_mouse;
  465. vec3 palette(float t,vec3 a,vec3 b,vec3 c,vec3 d){return a+b*cos(6.28318*(c*t+d));}
  466. void main(){
  467. vec2 uv=gl_FragCoord.xy/u_resolution.xy;
  468. vec2 p=uv*2.0-1.0;p.x*=u_resolution.x/u_resolution.y;
  469. vec2 m=u_mouse*2.0-1.0;m.x*=u_resolution.x/u_resolution.y;
  470. float md=length(p-m);
  471. float mr=sin(md*15.0-u_time*4.0)*exp(-md*3.0);p+=mr*0.08;
  472. vec2 p0=p;
  473. for(float i=1.0;i<4.0;i++){
  474. p.x+=0.1/i*sin(i*3.0*p.y+u_time*0.4)+0.05;
  475. p.y+=0.1/i*cos(i*2.0*p.x+u_time*0.3)-0.05;
  476. }
  477. float r=length(p);float ang=atan(p.y,p.x);
  478. vec3 a=vec3(0.12,0.12,0.13);
  479. vec3 b=vec3(0.03,0.04,0.05);
  480. vec3 c=vec3(1.0,1.0,1.0);
  481. vec3 d=vec3(0.1,0.2,0.4);
  482. vec3 col=palette(r*1.5+p0.x*0.5+u_time*0.1,a,b,c,d);
  483. float disp=sin(r*25.0-u_time*1.5+ang*2.0)*0.5+0.5;
  484. col+=vec3(disp*0.015,disp*0.01,disp*0.02);
  485. float hi=pow(sin(p.x*4.0+p.y*3.0+u_time)*0.5+0.5,8.0);
  486. col+=hi*0.08;
  487. vec3 base=vec3(0.05,0.05,0.06);
  488. col=mix(base,col,0.85);
  489. gl_FragColor=vec4(col,1.0);
  490. }`;
  491. const FS_LIGHT = `precision highp float;
  492. uniform vec2 u_resolution;uniform float u_time;uniform vec2 u_mouse;
  493. float hash(vec2 p){return fract(sin(dot(p,vec2(127.1,311.7)))*43758.5453);}
  494. float noise(vec2 p){
  495. vec2 i=floor(p),f=fract(p);
  496. float a=hash(i),b=hash(i+vec2(1,0));
  497. float c=hash(i+vec2(0,1)),d=hash(i+vec2(1,1));
  498. vec2 u=f*f*(3.0-2.0*f);
  499. return mix(a,b,u.x)+(c-a)*u.y*(1.0-u.x)+(d-b)*u.x*u.y;
  500. }
  501. float fbm(vec2 p){
  502. float v=0.0,a=0.5;
  503. mat2 m=mat2(0.80,0.60,-0.60,0.80);
  504. for(int i=0;i<5;i++){v+=a*noise(p);p=m*p*2.02;a*=0.5;}
  505. return v;
  506. }
  507. void main(){
  508. vec2 uv=gl_FragCoord.xy/u_resolution.xy;
  509. vec2 p=uv;p.x*=u_resolution.x/u_resolution.y;
  510. vec2 m=u_mouse;m.x*=u_resolution.x/u_resolution.y;
  511. vec2 md=p-m;float dl=length(md);
  512. p+=normalize(md+vec2(0.0001))*exp(-dl*5.0)*0.03;
  513. vec2 q=vec2(fbm(p*1.8+u_time*0.07),fbm(p*1.8+vec2(5.2,1.3)+u_time*0.06));
  514. vec2 r=vec2(fbm(p*2.0+q*1.3+vec2(1.7,9.2)+u_time*0.05),
  515. fbm(p*2.0+q*1.3+vec2(8.3,2.8)+u_time*0.04));
  516. float f=fbm(p*2.2+r*1.5);
  517. vec3 silverDark=vec3(0.86,0.85,0.84);
  518. vec3 paper=vec3(0.955,0.945,0.925);
  519. vec3 col=mix(silverDark,paper,f);
  520. float ph=r.x*2.2+u_time*0.35;
  521. col+=vec3(0.78,0.62,0.92)*sin(ph)*0.055;
  522. col+=vec3(0.55,0.72,0.95)*sin(ph*0.8+2.0)*0.05;
  523. float hl=smoothstep(0.48,0.92,f);
  524. col+=hl*0.06;
  525. gl_FragColor=vec4(col,1.0);
  526. }`;
  527. const mouse={x:0.5,y:0.5};
  528. addEventListener('mousemove',e=>{mouse.x=e.clientX/innerWidth;mouse.y=e.clientY/innerHeight});
  529. function bootGL(canvasId, fsSrc){
  530. const canvas=document.getElementById(canvasId);
  531. const gl=canvas.getContext('webgl',{alpha:false,antialias:true});
  532. if(!gl) return ()=>false;
  533. const mk=(t,s)=>{const sh=gl.createShader(t);gl.shaderSource(sh,s);gl.compileShader(sh);return sh};
  534. const prog=gl.createProgram();
  535. gl.attachShader(prog,mk(gl.VERTEX_SHADER,VS));
  536. gl.attachShader(prog,mk(gl.FRAGMENT_SHADER,fsSrc));
  537. gl.linkProgram(prog);gl.useProgram(prog);
  538. const buf=gl.createBuffer();
  539. gl.bindBuffer(gl.ARRAY_BUFFER,buf);
  540. gl.bufferData(gl.ARRAY_BUFFER,new Float32Array([-1,-1,1,-1,-1,1,-1,1,1,-1,1,1]),gl.STATIC_DRAW);
  541. const pos=gl.getAttribLocation(prog,'position');
  542. gl.enableVertexAttribArray(pos);gl.vertexAttribPointer(pos,2,gl.FLOAT,false,0,0);
  543. const lRes=gl.getUniformLocation(prog,'u_resolution');
  544. const lT=gl.getUniformLocation(prog,'u_time');
  545. const lM=gl.getUniformLocation(prog,'u_mouse');
  546. const resize=()=>{
  547. const d=Math.min(window.devicePixelRatio||1,2);
  548. canvas.width=innerWidth*d;canvas.height=innerHeight*d;
  549. gl.viewport(0,0,canvas.width,canvas.height);
  550. };
  551. addEventListener('resize',resize);resize();
  552. return (tSec)=>{
  553. gl.uniform2f(lRes,canvas.width,canvas.height);
  554. gl.uniform1f(lT,tSec);
  555. gl.uniform2f(lM,mouse.x,1-mouse.y);
  556. gl.drawArrays(gl.TRIANGLES,0,6);
  557. return true;
  558. };
  559. }
  560. let drawDark=null, drawLight=null, glRAF=0, glT0=Date.now();
  561. function startGL(){
  562. if(window.__lowPowerMode || glRAF) return;
  563. if(!drawDark) drawDark=bootGL('bg-dark',FS_DARK);
  564. if(!drawLight) drawLight=bootGL('bg-light',FS_LIGHT);
  565. glT0=Date.now();
  566. function loop(){
  567. if(window.__lowPowerMode){glRAF=0;return;}
  568. const t=(Date.now()-glT0)/1000;
  569. drawDark(t);drawLight(t);
  570. glRAF=requestAnimationFrame(loop);
  571. }
  572. glRAF=requestAnimationFrame(loop);
  573. }
  574. function stopGL(){
  575. if(glRAF) cancelAnimationFrame(glRAF);
  576. glRAF=0;
  577. }
  578. startGL();
  579. addEventListener('ppt-low-power-change', e=>{e.detail.on ? stopGL() : startGL();});
  580. // =============== 导航(翻页 / 圆点 / 键盘 / 滚轮 / 触屏) ===============
  581. const deck=document.getElementById('deck');
  582. const slides=deck.querySelectorAll('.slide');
  583. const nav=document.getElementById('nav');
  584. let idx=0,total=slides.length,lock=false;
  585. // 关键:矫正 deck 宽度为 total * 100vw,否则翻页会错位
  586. deck.style.width=(total*100)+'vw';
  587. slides.forEach((s,i)=>{
  588. const b=document.createElement('button');
  589. b.className='dot';b.dataset.i=i;b.setAttribute('aria-label','Page '+(i+1));
  590. b.onclick=()=>go(i);
  591. nav.appendChild(b);
  592. });
  593. function go(n){
  594. if(lock)return;
  595. idx=Math.max(0,Math.min(total-1,n));
  596. window.__currentSlideIndex = idx;
  597. deck.style.transform=`translateX(${-idx*100}vw)`;
  598. nav.querySelectorAll('.dot').forEach((d,i)=>d.classList.toggle('active',i===idx));
  599. /* 主题切换:优先读 data-theme,其次从 class(light/dark)推断 */
  600. const el=slides[idx];
  601. const th=el.dataset.theme || (el.classList.contains('light')?'light':(el.classList.contains('dark')?'dark':'dark'));
  602. document.body.classList.toggle('light-bg',th==='light');
  603. /* 动效:翻页过渡中段触发当前页的入场动画(由 motion-boot 注册) */
  604. if(window.__playSlide) setTimeout(()=>window.__playSlide(idx), 450);
  605. lock=true;setTimeout(()=>lock=false,700);
  606. }
  607. /* =============== ESC 索引视图 =============== */
  608. let overviewOn=false;
  609. const ov=document.createElement('div');
  610. ov.id='overview';
  611. ov.style.cssText='position:fixed;inset:0;z-index:100;background:rgba(var(--ink-rgb),.92);backdrop-filter:blur(12px);display:none;overflow-y:auto;padding:4vh 4vw';
  612. document.body.appendChild(ov);
  613. function buildOverview(){
  614. ov.innerHTML='';
  615. const grid=document.createElement('div');
  616. grid.style.cssText='display:grid;grid-template-columns:repeat(4,1fr);gap:2vh 1.6vw;max-width:90vw;margin:0 auto';
  617. slides.forEach((s,i)=>{
  618. const card=document.createElement('div');
  619. card.style.cssText='cursor:pointer;border-radius:6px;overflow:hidden;border:2px solid '+(i===idx?'rgba(var(--paper-rgb),.8)':'rgba(var(--paper-rgb),.15)')+';transition:border-color .2s';
  620. card.onmouseenter=()=>card.style.borderColor='rgba(var(--paper-rgb),.6)';
  621. card.onmouseleave=()=>card.style.borderColor=i===idx?'rgba(var(--paper-rgb),.8)':'rgba(var(--paper-rgb),.15)';
  622. const wrap=document.createElement('div');
  623. wrap.style.cssText='width:100%;aspect-ratio:16/9;overflow:hidden;position:relative;pointer-events:none;background:'+(s.classList.contains('light')?'var(--paper)':'var(--ink)');
  624. const clone=s.cloneNode(true);
  625. 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';
  626. wrap.appendChild(clone);
  627. const label=document.createElement('div');
  628. label.style.cssText='padding:6px 10px;font-family:var(--mono);font-size:11px;letter-spacing:.18em;text-transform:uppercase;color:var(--paper);opacity:.7';
  629. label.textContent=(i+1)+' / '+total;
  630. card.appendChild(wrap);
  631. card.appendChild(label);
  632. card.onclick=()=>{toggleOverview();go(i)};
  633. grid.appendChild(card);
  634. });
  635. ov.appendChild(grid);
  636. }
  637. function toggleOverview(){
  638. overviewOn=!overviewOn;
  639. if(overviewOn){buildOverview();ov.style.display='block';}
  640. else{ov.style.display='none';}
  641. }
  642. addEventListener('keydown',e=>{
  643. if(e.key==='Escape'){e.preventDefault();toggleOverview();return;}
  644. if(e.key && e.key.toLowerCase()==='b' && !e.metaKey && !e.ctrlKey && !e.altKey){
  645. e.preventDefault();
  646. window.__setLowPowerMode(!window.__lowPowerMode);
  647. return;
  648. }
  649. if(overviewOn)return;
  650. if(e.key==='ArrowRight'||e.key==='PageDown'||e.key===' '||e.key==='ArrowDown'){
  651. if(window.__pipeAdvance && window.__pipeAdvance()) return;
  652. go(idx+1);
  653. return;
  654. }
  655. if(e.key==='ArrowLeft'||e.key==='PageUp'||e.key==='ArrowUp')go(idx-1);
  656. if(e.key==='Home')go(0);
  657. if(e.key==='End')go(total-1);
  658. });
  659. let wheelTO=null,wheelAcc=0;
  660. addEventListener('wheel',e=>{
  661. wheelAcc+=e.deltaY+e.deltaX;
  662. if(Math.abs(wheelAcc)>50){
  663. if(wheelAcc>0 && window.__pipeAdvance && window.__pipeAdvance()){
  664. wheelAcc=0;
  665. }else{
  666. go(idx+(wheelAcc>0?1:-1));wheelAcc=0;
  667. }
  668. }
  669. clearTimeout(wheelTO);wheelTO=setTimeout(()=>wheelAcc=0,150);
  670. },{passive:true});
  671. let tx=0,ty=0;
  672. addEventListener('touchstart',e=>{tx=e.touches[0].clientX;ty=e.touches[0].clientY},{passive:true});
  673. addEventListener('touchend',e=>{
  674. const dx=(e.changedTouches[0].clientX-tx);
  675. const dy=(e.changedTouches[0].clientY-ty);
  676. if(Math.abs(dx)>50&&Math.abs(dx)>Math.abs(dy)){
  677. if(dx<0 && window.__pipeAdvance && window.__pipeAdvance()) return;
  678. go(idx+(dx<0?1:-1));
  679. }
  680. },{passive:true});
  681. go(0);
  682. </script>
  683. <script src="https://unpkg.com/lucide@latest/dist/umd/lucide.min.js"></script>
  684. <script>lucide.createIcons();</script>
  685. <!-- ============ Motion One 动效引擎(本地优先,CDN 兜底) ============
  686. 加载策略:先 ./assets/motion.min.js(本地,离线可用),失败 fallback 到 jsDelivr CDN
  687. 加载完成后挂 window.__playSlide / window.__pipeAdvance,
  688. 导航脚本会在翻页中段调用 __playSlide(idx),键盘/滚轮/触屏会在翻页前调用 __pipeAdvance()
  689. 双双失败时会兜底把所有 [data-anim] 设为可见,不让动效破坏阅读
  690. -->
  691. <script type="module">
  692. let motion;
  693. try {
  694. motion = await import('./assets/motion.min.js');
  695. } catch(e1) {
  696. try {
  697. motion = await import('https://cdn.jsdelivr.net/npm/motion@11.11.17/+esm');
  698. } catch(e2) {
  699. console.warn('[motion] local + CDN both failed, disabling animations', e1, e2);
  700. document.querySelectorAll('[data-anim]').forEach(el=>{el.style.opacity='1';el.style.transform='none'});
  701. document.querySelectorAll('[data-animate="pipeline"] [data-anim]').forEach(el=>el.style.opacity='1');
  702. }
  703. }
  704. if(motion){
  705. const { animate, stagger } = motion;
  706. document.body.classList.add('motion-ready');
  707. const EASE = [.22, 1, .36, 1];
  708. const slides = [...document.querySelectorAll('.slide')];
  709. let pipeStep = -1;
  710. let lastIdx = -1;
  711. function resetAnims(slide){
  712. slide.querySelectorAll('[data-anim]').forEach(el=>{
  713. el.style.opacity='';
  714. el.style.transform='';
  715. });
  716. }
  717. function revealStatic(slide){
  718. resetAnims(slide);
  719. document.getAnimations?.().forEach(a=>a.cancel());
  720. slide.querySelectorAll('[data-anim]').forEach(el=>{
  721. el.style.opacity='1';
  722. el.style.transform='none';
  723. });
  724. }
  725. function playSlide(i){
  726. const slide = slides[i];
  727. if(!slide) return;
  728. lastIdx = i;
  729. const recipe = slide.dataset.animate || (slide.classList.contains('hero') ? 'hero' : 'cascade');
  730. if(window.__lowPowerMode){
  731. revealStatic(slide);
  732. return;
  733. }
  734. if(recipe === 'pipeline'){
  735. pipeStep = -1;
  736. slide.querySelectorAll('[data-anim]').forEach(el=>{
  737. el.style.opacity='0.15';
  738. el.style.transform='none';
  739. });
  740. return;
  741. }
  742. resetAnims(slide);
  743. const all = [...slide.querySelectorAll('[data-anim]')];
  744. if(!all.length) return;
  745. if(recipe === 'directional'){
  746. const lefts = all.filter(el=>el.dataset.anim==='left');
  747. const divs = all.filter(el=>el.dataset.anim==='divider');
  748. const rights = all.filter(el=>el.dataset.anim==='right');
  749. const others = all.filter(el=>!['left','right','divider'].includes(el.dataset.anim));
  750. if(others.length) animate(others, {opacity:[0,1], y:[12,0]}, {duration:.6, delay:stagger(.1, {start:.15}), easing:EASE});
  751. if(lefts.length) animate(lefts, {opacity:[0,1], x:[-24,0]}, {duration:.8, delay:.35, easing:EASE});
  752. if(divs.length) animate(divs, {opacity:[0,.25]}, {duration:.5, delay:.9});
  753. if(rights.length) animate(rights, {opacity:[0,1], x:[24,0]}, {duration:.8, delay:1.0, easing:EASE});
  754. return;
  755. }
  756. if(recipe === 'quote'){
  757. const lines = all.filter(el=>el.dataset.anim==='line');
  758. const others = all.filter(el=>el.dataset.anim!=='line');
  759. if(others.length) animate(others, {opacity:[0,1], y:[8,0]}, {duration:.6, delay:stagger(.12, {start:.2}), easing:EASE});
  760. if(lines.length) animate(lines, {opacity:[.35,1], y:[10,0]}, {duration:.8, delay:stagger(.55, {start:.5}), easing:EASE});
  761. return;
  762. }
  763. if(recipe === 'hero'){
  764. animate(all, {opacity:[0,1], y:[14,0]}, {duration:.9, delay:stagger(.16, {start:.2}), easing:EASE});
  765. return;
  766. }
  767. // default: cascade
  768. animate(all, {opacity:[0,1], y:[16,0]}, {duration:.75, delay:stagger(.1, {start:.15}), easing:EASE});
  769. }
  770. function pipeAdvance(){
  771. if(window.__lowPowerMode) return false;
  772. const slide = slides[lastIdx];
  773. if(!slide || slide.dataset.animate !== 'pipeline') return false;
  774. const steps = [...slide.querySelectorAll('[data-anim="step"]')];
  775. const arrows = [...slide.querySelectorAll('[data-anim="arrow"]')];
  776. if(pipeStep >= steps.length - 1) return false;
  777. pipeStep++;
  778. animate(steps[pipeStep], {opacity:[0.15,1], y:[8,0]}, {duration:.5, easing:EASE});
  779. if(pipeStep > 0 && arrows[pipeStep-1]){
  780. animate(arrows[pipeStep-1], {opacity:[0.15,.7]}, {duration:.3, delay:.15});
  781. }
  782. return true;
  783. }
  784. window.__playSlide = playSlide;
  785. window.__pipeAdvance = pipeAdvance;
  786. // 首屏:go(0) 已跑过(同步),没来得及触发动效 → 这里补一下
  787. playSlide(0);
  788. }
  789. </script>
  790. </body>
  791. </html>