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