feat: integrate Motion One animation system (default-on)
- Add assets/motion.min.js (local fallback for offline demos) - template.html: embed loader with local + CDN fallback chain, 5 recipes (cascade / hero / quote / directional / pipeline) - layouts.md: add data-anim markers to all 10 layouts, data-animate on pipeline / quote / directional layouts, Pre-flight section E documenting recipe decision tree - components.md: add Motion 动效系统 chapter with loader, data attributes, recipes table, decision tree, FAQ - checklist.md: add P1 entries 9b/9c and a 动效 self-check block - SKILL.md: document animations in features list, resource tree, and loading order hints Pipeline pages use manual advance: → / space / wheel-down light up steps one by one; final → advances to next slide.
This commit is contained in:
1 parent
a593cf4b73
commit
e09f931308
6 files changed
+337
-69
No files matched your search
@@ -15,6 +15,7 @@ description: 生成"电子杂志 × 电子墨水"风格的横向翻页网页 PPT
|
|||||||
- **Lucide 线性图标**(不用 emoji)
|
- **Lucide 线性图标**(不用 emoji)
|
||||||
- **横向左右翻页**(键盘 ← →、滚轮、触屏滑动、底部圆点、ESC 索引)
|
- **横向左右翻页**(键盘 ← →、滚轮、触屏滑动、底部圆点、ESC 索引)
|
||||||
- **主题平滑插值**:翻到 hero 页时颜色和 shader 柔顺过渡
|
- **主题平滑插值**:翻到 hero 页时颜色和 shader 柔顺过渡
|
||||||
|
- **翻页入场动效**(Motion One 驱动,5 种 recipe 自动匹配布局,本地 + CDN 双保险,离线可用)
|
||||||
|
|
||||||
这个 skill 的美学不是"商务 PPT",也不是"消费互联网 UI"——它像 *Monocle* 杂志贴上了代码后的样子。
|
这个 skill 的美学不是"商务 PPT",也不是"消费互联网 UI"——它像 *Monocle* 杂志贴上了代码后的样子。
|
||||||
|
|
||||||
@@ -226,10 +227,11 @@ open "项目/XXX/ppt/index.html"
|
|||||||
guizang-ppt-skill/
|
guizang-ppt-skill/
|
||||||
├── SKILL.md ← 你正在读
|
├── SKILL.md ← 你正在读
|
||||||
├── assets/
|
├── assets/
|
||||||
│ └── template.html ← 完整的可运行模板(种子文件)
|
│ ├── template.html ← 完整的可运行模板(种子文件)
|
||||||
|
│ └── motion.min.js ← Motion One 本地副本(离线兜底,约 64KB)
|
||||||
└── references/
|
└── references/
|
||||||
├── components.md ← 组件手册(字体、色、网格、图标、callout、stat、pipeline...)
|
├── components.md ← 组件手册(字体、色、网格、图标、callout、stat、pipeline、动效...)
|
||||||
├── layouts.md ← 10 种页面布局骨架(可直接粘贴)
|
├── layouts.md ← 10 种页面布局骨架(可直接粘贴,含动效标记)
|
||||||
├── themes.md ← 5 套主题色预设(只能选不能自定义)
|
├── themes.md ← 5 套主题色预设(只能选不能自定义)
|
||||||
└── checklist.md ← 质量检查清单(P0/P1/P2/P3 分级)
|
└── checklist.md ← 质量检查清单(P0/P1/P2/P3 分级)
|
||||||
```
|
```
|
||||||
@@ -238,9 +240,11 @@ guizang-ppt-skill/
|
|||||||
1. 先读完 `SKILL.md`(这个文件)了解整体
|
1. 先读完 `SKILL.md`(这个文件)了解整体
|
||||||
2. Step 1 需求澄清完成后,读 `themes.md` 帮用户选定一套主题色
|
2. Step 1 需求澄清完成后,读 `themes.md` 帮用户选定一套主题色
|
||||||
3. **动手前 Read `assets/template.html` 的 `<style>` 块**——这是类名的唯一来源,缺类会导致整页样式崩
|
3. **动手前 Read `assets/template.html` 的 `<style>` 块**——这是类名的唯一来源,缺类会导致整页样式崩
|
||||||
4. 读 `layouts.md` 挑布局(顶部有 Pre-flight 类名清单和主题节奏规划)
|
4. 读 `layouts.md` 挑布局(顶部有 Pre-flight 类名清单、主题节奏规划、动效 recipe 决策树)
|
||||||
5. 细节调整时读 `components.md` 查组件
|
5. 细节调整时读 `components.md` 查组件(含 Motion 动效系统章节)
|
||||||
6. 生成后读 `checklist.md` 自检(顶部 P0-0 规则强制预检)
|
6. 生成后读 `checklist.md` 自检(顶部 P0-0 规则强制预检 + 动效自检块)
|
||||||
|
|
||||||
|
**动效相关**:模板已把 Motion One 的加载和 5 种 recipe 逻辑全部内嵌到 `template.html` 底部的 module script。你不需要改 JS,只需要按 `layouts.md` 的骨架在 HTML 里加 `data-anim` / `data-animate` 即可。离线演示靠 `assets/motion.min.js`,断网时自动降级为"无动画但内容可读"。
|
||||||
|
|
||||||
## 核心设计原则(哲学)
|
## 核心设计原则(哲学)
|
||||||
|
|
||||||
|
|||||||
Vendored
+8
@@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* Bundled by jsDelivr using Rollup v2.79.2 and Terser v5.39.0.
|
||||||
|
* Original file: /npm/motion@11.11.17/dist/es/motion/lib/index.mjs
|
||||||
|
*
|
||||||
|
* Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
|
||||||
|
*/
|
||||||
|
function t(t,e){const n=t.indexOf(e);n>-1&&t.splice(n,1)}class e{constructor(){this.subscriptions=[]}add(e){var n,s;return n=this.subscriptions,s=e,-1===n.indexOf(s)&&n.push(s),()=>t(this.subscriptions,e)}notify(t,e,n){const s=this.subscriptions.length;if(s)if(1===s)this.subscriptions[0](t,e,n);else for(let i=0;i<s;i++){const s=this.subscriptions[i];s&&s(t,e,n)}}getSize(){return this.subscriptions.length}clear(){this.subscriptions.length=0}}function n(t,e){return e?t*(1e3/e):0}const s=!1,i=t=>t;const r=["read","resolveKeyframes","update","preRender","render","postRender"];const{schedule:o,cancel:a,state:l,steps:u}=function(t,e){let n=!1,s=!0;const i={delta:0,timestamp:0,isProcessing:!1},o=()=>n=!0,a=r.reduce(((t,e)=>(t[e]=function(t){let e=new Set,n=new Set,s=!1,i=!1;const r=new WeakSet;let o={delta:0,timestamp:0,isProcessing:!1};function a(e){r.has(e)&&(l.schedule(e),t()),e(o)}const l={schedule:(t,i=!1,o=!1)=>{const a=o&&s?e:n;return i&&r.add(t),a.has(t)||a.add(t),t},cancel:t=>{n.delete(t),r.delete(t)},process:t=>{o=t,s?i=!0:(s=!0,[e,n]=[n,e],n.clear(),e.forEach(a),s=!1,i&&(i=!1,l.process(t)))}};return l}(o),t)),{}),{read:l,resolveKeyframes:u,update:c,preRender:h,render:d,postRender:p}=a,f=()=>{const r=performance.now();n=!1,i.delta=s?1e3/60:Math.max(Math.min(r-i.timestamp,40),1),i.timestamp=r,i.isProcessing=!0,l.process(i),u.process(i),c.process(i),h.process(i),d.process(i),p.process(i),i.isProcessing=!1,n&&e&&(s=!1,t(f))};return{schedule:r.reduce(((e,r)=>{const o=a[r];return e[r]=(e,r=!1,a=!1)=>(n||(n=!0,s=!0,i.isProcessing||t(f)),o.schedule(e,r,a)),e}),{}),cancel:t=>{for(let e=0;e<r.length;e++)a[r[e]].cancel(t)},state:i,steps:a}}("undefined"!=typeof requestAnimationFrame?requestAnimationFrame:i,!0);let c;function h(){c=void 0}const d={now:()=>(void 0===c&&d.set(l.isProcessing||s?l.timestamp:performance.now()),c),set:t=>{c=t,queueMicrotask(h)}};class p{constructor(t,e={}){this.version="11.11.17",this.canTrackVelocity=null,this.events={},this.updateAndNotify=(t,e=!0)=>{const n=d.now();this.updatedAt!==n&&this.setPrevFrameValue(),this.prev=this.current,this.setCurrent(t),this.current!==this.prev&&this.events.change&&this.events.change.notify(this.current),e&&this.events.renderRequest&&this.events.renderRequest.notify(this.current)},this.hasAnimated=!1,this.setCurrent(t),this.owner=e.owner}setCurrent(t){var e;this.current=t,this.updatedAt=d.now(),null===this.canTrackVelocity&&void 0!==t&&(this.canTrackVelocity=(e=this.current,!isNaN(parseFloat(e))))}setPrevFrameValue(t=this.current){this.prevFrameValue=t,this.prevUpdatedAt=this.updatedAt}onChange(t){return this.on("change",t)}on(t,n){this.events[t]||(this.events[t]=new e);const s=this.events[t].add(n);return"change"===t?()=>{s(),o.read((()=>{this.events.change.getSize()||this.stop()}))}:s}clearListeners(){for(const t in this.events)this.events[t].clear()}attach(t,e){this.passiveEffect=t,this.stopPassiveEffect=e}set(t,e=!0){e&&this.passiveEffect?this.passiveEffect(t,this.updateAndNotify):this.updateAndNotify(t,e)}setWithVelocity(t,e,n){this.set(e),this.prev=void 0,this.prevFrameValue=t,this.prevUpdatedAt=this.updatedAt-n}jump(t,e=!0){this.updateAndNotify(t),this.prev=t,this.prevUpdatedAt=this.prevFrameValue=void 0,e&&this.stop(),this.stopPassiveEffect&&this.stopPassiveEffect()}get(){return this.current}getPrevious(){return this.prev}getVelocity(){const t=d.now();if(!this.canTrackVelocity||void 0===this.prevFrameValue||t-this.updatedAt>30)return 0;const e=Math.min(this.updatedAt-this.prevUpdatedAt,30);return n(parseFloat(this.current)-parseFloat(this.prevFrameValue),e)}start(t){return this.stop(),new Promise((e=>{this.hasAnimated=!0,this.animation=t(e),this.events.animationStart&&this.events.animationStart.notify()})).then((()=>{this.events.animationComplete&&this.events.animationComplete.notify(),this.clearAnimation()}))}stop(){this.animation&&(this.animation.stop(),this.events.animationCancel&&this.events.animationCancel.notify()),this.clearAnimation()}isAnimating(){return!!this.animation}clearAnimation(){delete this.animation}destroy(){this.clearListeners(),this.stop(),this.stopPassiveEffect&&this.stopPassiveEffect()}}function f(t,e){return new p(t,e)}function m(t){let e;return()=>(void 0===e&&(e=t()),e)}const g=m((()=>void 0!==window.ScrollTimeline));class y{constructor(t){this.stop=()=>this.runAll("stop"),this.animations=t.filter(Boolean)}then(t,e){return Promise.all(this.animations).then(t).catch(e)}getAll(t){return this.animations[0][t]}setAll(t,e){for(let n=0;n<this.animations.length;n++)this.animations[n][t]=e}attachTimeline(t,e){const n=this.animations.map((n=>g()&&n.attachTimeline?n.attachTimeline(t):e(n)));return()=>{n.forEach(((t,e)=>{t&&t(),this.animations[e].stop()}))}}get time(){return this.getAll("time")}set time(t){this.setAll("time",t)}get speed(){return this.getAll("speed")}set speed(t){this.setAll("speed",t)}get startTime(){return this.getAll("startTime")}get duration(){let t=0;for(let e=0;e<this.animations.length;e++)t=Math.max(t,this.aLine truncated
|
||||||
|
//# sourceMappingURL=/sm/d5d4bfa5a3f292edc74ce2b856e25f03ebbfa4c4c22aa37e3f75ca1dabe374b0.map
|
||||||
+137
-3
@@ -396,6 +396,21 @@
|
|||||||
.chrome{font-family:var(--mono);font-size:max(11px,.78vw);letter-spacing:.2em;text-transform:uppercase;opacity:.62}
|
.chrome{font-family:var(--mono);font-size:max(11px,.78vw);letter-spacing:.2em;text-transform:uppercase;opacity:.62}
|
||||||
.foot{font-family:var(--mono);font-size:max(11px,.78vw);letter-spacing:.18em;text-transform:uppercase;opacity:.5}
|
.foot{font-family:var(--mono);font-size:max(11px,.78vw);letter-spacing:.18em;text-transform:uppercase;opacity:.5}
|
||||||
|
|
||||||
|
/* ============ 动效系统(Motion One 驱动) ============
|
||||||
|
所有 [data-anim] 元素默认隐藏,进入页面时由 JS 逐个揭示。
|
||||||
|
- cascade(默认):按 DOM 顺序 stagger 120ms 淡入 + y:16→0
|
||||||
|
- hero(hero 页自动):慢一点的 stagger 180ms
|
||||||
|
- quote(含 [data-anim="line"]):逐行 600ms 淡入
|
||||||
|
- directional(含 [data-anim="left|right"]):左→分隔线→右
|
||||||
|
- pipeline(data-animate="pipeline"):Space/→ 手动推进
|
||||||
|
Motion One 没加载成功时(CDN 断 + 本地丢),所有 [data-anim] 会兜底显示。
|
||||||
|
*/
|
||||||
|
[data-anim]{opacity:0}
|
||||||
|
[data-anim="left"]{transform:translateX(-24px)}
|
||||||
|
[data-anim="right"]{transform:translateX(24px)}
|
||||||
|
[data-anim="line"]{opacity:0;transform:translateY(10px)}
|
||||||
|
[data-animate="pipeline"] [data-anim]{opacity:.15}
|
||||||
|
|
||||||
/* ---------- 响应式降级 ---------- */
|
/* ---------- 响应式降级 ---------- */
|
||||||
@media (max-width:900px){
|
@media (max-width:900px){
|
||||||
.display{font-size:16vw}
|
.display{font-size:16vw}
|
||||||
@@ -570,6 +585,8 @@ function go(n){
|
|||||||
const el=slides[idx];
|
const el=slides[idx];
|
||||||
const th=el.dataset.theme || (el.classList.contains('light')?'light':(el.classList.contains('dark')?'dark':'dark'));
|
const th=el.dataset.theme || (el.classList.contains('light')?'light':(el.classList.contains('dark')?'dark':'dark'));
|
||||||
document.body.classList.toggle('light-bg',th==='light');
|
document.body.classList.toggle('light-bg',th==='light');
|
||||||
|
/* 动效:翻页过渡中段触发当前页的入场动画(由 motion-boot 注册) */
|
||||||
|
if(window.__playSlide) setTimeout(()=>window.__playSlide(idx), 450);
|
||||||
lock=true;setTimeout(()=>lock=false,700);
|
lock=true;setTimeout(()=>lock=false,700);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -614,7 +631,11 @@ function toggleOverview(){
|
|||||||
addEventListener('keydown',e=>{
|
addEventListener('keydown',e=>{
|
||||||
if(e.key==='Escape'){e.preventDefault();toggleOverview();return;}
|
if(e.key==='Escape'){e.preventDefault();toggleOverview();return;}
|
||||||
if(overviewOn)return;
|
if(overviewOn)return;
|
||||||
if(e.key==='ArrowRight'||e.key==='PageDown'||e.key===' '||e.key==='ArrowDown')go(idx+1);
|
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==='ArrowLeft'||e.key==='PageUp'||e.key==='ArrowUp')go(idx-1);
|
||||||
if(e.key==='Home')go(0);
|
if(e.key==='Home')go(0);
|
||||||
if(e.key==='End')go(total-1);
|
if(e.key==='End')go(total-1);
|
||||||
@@ -623,7 +644,13 @@ addEventListener('keydown',e=>{
|
|||||||
let wheelTO=null,wheelAcc=0;
|
let wheelTO=null,wheelAcc=0;
|
||||||
addEventListener('wheel',e=>{
|
addEventListener('wheel',e=>{
|
||||||
wheelAcc+=e.deltaY+e.deltaX;
|
wheelAcc+=e.deltaY+e.deltaX;
|
||||||
if(Math.abs(wheelAcc)>50){go(idx+(wheelAcc>0?1:-1));wheelAcc=0;}
|
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);
|
clearTimeout(wheelTO);wheelTO=setTimeout(()=>wheelAcc=0,150);
|
||||||
},{passive:true});
|
},{passive:true});
|
||||||
|
|
||||||
@@ -632,12 +659,119 @@ addEventListener('touchstart',e=>{tx=e.touches[0].clientX;ty=e.touches[0].client
|
|||||||
addEventListener('touchend',e=>{
|
addEventListener('touchend',e=>{
|
||||||
const dx=(e.changedTouches[0].clientX-tx);
|
const dx=(e.changedTouches[0].clientX-tx);
|
||||||
const dy=(e.changedTouches[0].clientY-ty);
|
const dy=(e.changedTouches[0].clientY-ty);
|
||||||
if(Math.abs(dx)>50&&Math.abs(dx)>Math.abs(dy))go(idx+(dx<0?1:-1));
|
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});
|
},{passive:true});
|
||||||
|
|
||||||
go(0);
|
go(0);
|
||||||
</script>
|
</script>
|
||||||
<script src="https://unpkg.com/lucide@latest/dist/umd/lucide.min.js"></script>
|
<script src="https://unpkg.com/lucide@latest/dist/umd/lucide.min.js"></script>
|
||||||
<script>lucide.createIcons();</script>
|
<script>lucide.createIcons();</script>
|
||||||
|
|
||||||
|
<!-- ============ Motion One 动效引擎(本地优先,CDN 兜底) ============
|
||||||
|
加载策略:先 ./assets/motion.min.js(本地,离线可用),失败 fallback 到 jsDelivr CDN
|
||||||
|
加载完成后挂 window.__playSlide / window.__pipeAdvance,
|
||||||
|
导航脚本会在翻页中段调用 __playSlide(idx),键盘/滚轮/触屏会在翻页前调用 __pipeAdvance()
|
||||||
|
双双失败时会兜底把所有 [data-anim] 设为可见,不让动效破坏阅读
|
||||||
|
-->
|
||||||
|
<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, stagger } = motion;
|
||||||
|
const EASE = [.22, 1, .36, 1];
|
||||||
|
const slides = [...document.querySelectorAll('.slide')];
|
||||||
|
let pipeStep = -1;
|
||||||
|
let lastIdx = -1;
|
||||||
|
|
||||||
|
function resetAnims(slide){
|
||||||
|
slide.querySelectorAll('[data-anim]').forEach(el=>{
|
||||||
|
el.style.opacity='';
|
||||||
|
el.style.transform='';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function playSlide(i){
|
||||||
|
const slide = slides[i];
|
||||||
|
if(!slide) return;
|
||||||
|
lastIdx = i;
|
||||||
|
const recipe = slide.dataset.animate || (slide.classList.contains('hero') ? 'hero' : 'cascade');
|
||||||
|
|
||||||
|
if(recipe === 'pipeline'){
|
||||||
|
pipeStep = -1;
|
||||||
|
slide.querySelectorAll('[data-anim]').forEach(el=>{
|
||||||
|
el.style.opacity='0.15';
|
||||||
|
el.style.transform='none';
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
resetAnims(slide);
|
||||||
|
const all = [...slide.querySelectorAll('[data-anim]')];
|
||||||
|
if(!all.length) return;
|
||||||
|
|
||||||
|
if(recipe === 'directional'){
|
||||||
|
const lefts = all.filter(el=>el.dataset.anim==='left');
|
||||||
|
const divs = all.filter(el=>el.dataset.anim==='divider');
|
||||||
|
const rights = all.filter(el=>el.dataset.anim==='right');
|
||||||
|
const others = all.filter(el=>!['left','right','divider'].includes(el.dataset.anim));
|
||||||
|
if(others.length) animate(others, {opacity:[0,1], y:[12,0]}, {duration:.6, delay:stagger(.1, {start:.15}), easing:EASE});
|
||||||
|
if(lefts.length) animate(lefts, {opacity:[0,1], x:[-24,0]}, {duration:.8, delay:.35, easing:EASE});
|
||||||
|
if(divs.length) animate(divs, {opacity:[0,.25]}, {duration:.5, delay:.9});
|
||||||
|
if(rights.length) animate(rights, {opacity:[0,1], x:[24,0]}, {duration:.8, delay:1.0, easing:EASE});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(recipe === 'quote'){
|
||||||
|
const lines = all.filter(el=>el.dataset.anim==='line');
|
||||||
|
const others = all.filter(el=>el.dataset.anim!=='line');
|
||||||
|
if(others.length) animate(others, {opacity:[0,1], y:[8,0]}, {duration:.6, delay:stagger(.12, {start:.2}), easing:EASE});
|
||||||
|
if(lines.length) animate(lines, {opacity:[.35,1], y:[10,0]}, {duration:.8, delay:stagger(.55, {start:.5}), easing:EASE});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(recipe === 'hero'){
|
||||||
|
animate(all, {opacity:[0,1], y:[14,0]}, {duration:.9, delay:stagger(.16, {start:.2}), easing:EASE});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// default: cascade
|
||||||
|
animate(all, {opacity:[0,1], y:[16,0]}, {duration:.75, delay:stagger(.1, {start:.15}), easing:EASE});
|
||||||
|
}
|
||||||
|
|
||||||
|
function pipeAdvance(){
|
||||||
|
const slide = slides[lastIdx];
|
||||||
|
if(!slide || slide.dataset.animate !== 'pipeline') return false;
|
||||||
|
const steps = [...slide.querySelectorAll('[data-anim="step"]')];
|
||||||
|
const arrows = [...slide.querySelectorAll('[data-anim="arrow"]')];
|
||||||
|
if(pipeStep >= steps.length - 1) return false;
|
||||||
|
pipeStep++;
|
||||||
|
animate(steps[pipeStep], {opacity:[0.15,1], y:[8,0]}, {duration:.5, easing:EASE});
|
||||||
|
if(pipeStep > 0 && arrows[pipeStep-1]){
|
||||||
|
animate(arrows[pipeStep-1], {opacity:[0.15,.7]}, {duration:.3, delay:.15});
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.__playSlide = playSlide;
|
||||||
|
window.__pipeAdvance = pipeAdvance;
|
||||||
|
|
||||||
|
// 首屏:go(0) 已跑过(同步),没来得及触发动效 → 这里补一下
|
||||||
|
playSlide(0);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -170,6 +170,30 @@ Hero Cover → Act Divider (hero) → 3-4 pages non-hero → Act Divider (hero)
|
|||||||
|
|
||||||
用 `XX / 总页数` 的格式(比如 `05 / 27`)。**不要在右上角加动态页码**(会和 `.chrome` 重复)。
|
用 `XX / 总页数` 的格式(比如 `05 / 27`)。**不要在右上角加动态页码**(会和 `.chrome` 重复)。
|
||||||
|
|
||||||
|
### 9b. 动效系统:每一页都要有 data-anim 标记
|
||||||
|
|
||||||
|
**现象**:生成后打开浏览器,翻页时内容直接"啪"地出来,没有任何节奏感——杂志风完全靠排版硬撑,少了层级展开的仪式感。
|
||||||
|
|
||||||
|
**根因**:完全没给任何元素加 `data-anim`,Motion One 脚本找不到可播的元素,整页静态出现。
|
||||||
|
|
||||||
|
**做法**:
|
||||||
|
- 所有正文页,**至少给 kicker / 主标题 / lead / callout / stat-card / figure 这些叶子元素加 `data-anim`**
|
||||||
|
- **Hero 页**(开场/幕封/问题/结尾):所有核心块(kicker + 大标题 + lead + meta-row)都要加
|
||||||
|
- **不需要特殊 recipe 的页**:什么也不用写,默认 cascade 就好看
|
||||||
|
- **需要特殊 recipe 的 4 类页**:必须在 `<section>` 上加对应 `data-animate`
|
||||||
|
- 大引用 → `data-animate="quote"` + 每行 `<span data-anim="line" style="display:block">`
|
||||||
|
- Before/After 对比 → `data-animate="directional"` + 左列 `data-anim="left"` + 右列 `data-anim="right"`
|
||||||
|
- Pipeline 流水线 → `data-animate="pipeline"` + 每 step 加 `data-anim="step"`
|
||||||
|
- Hero 页(自动用 hero recipe,但仍需给元素加 `data-anim`)
|
||||||
|
|
||||||
|
**自检**:生成后 `grep -c 'data-anim' index.html`,应该数十条以上。如果只有个位数,一定漏标了。
|
||||||
|
|
||||||
|
### 9c. Pipeline 页必须加 data-animate="pipeline"
|
||||||
|
|
||||||
|
**现象**:流水线页直接全部淡入,失去"一步步讲"的节奏,但切到下一页时又只能往前翻,没法回到上一个 step。
|
||||||
|
|
||||||
|
**做法**:Layout 6 的 `<section>` 必须加 `data-animate="pipeline"`。演示时按 →/空格/滚轮下滑可以**逐个点亮 step**,全部点亮之后再按 → 才会翻到下一页。这个节奏是刻意的,不是 bug。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🟢 P2 · 视觉打磨
|
## 🟢 P2 · 视觉打磨
|
||||||
@@ -260,6 +284,14 @@ JS 会动态算总页数并扩展底部翻页圆点,但 `.chrome` 里的 `XX /
|
|||||||
□ 底部圆点数量与总页数匹配
|
□ 底部圆点数量与总页数匹配
|
||||||
□ chrome 里的页码和实际页号一致
|
□ chrome 里的页码和实际页号一致
|
||||||
□ ESC 键触发索引视图(如果保留)
|
□ ESC 键触发索引视图(如果保留)
|
||||||
|
|
||||||
|
动效
|
||||||
|
□ `assets/motion.min.js` 存在(本地兜底)
|
||||||
|
□ 翻页时内容逐个淡入,不是"啪"一下全出
|
||||||
|
□ 大引用页 `<section>` 带 `data-animate="quote"`,每行 `<span data-anim="line">`
|
||||||
|
□ Before/After 对比页 `<section>` 带 `data-animate="directional"`,左右列标 left/right
|
||||||
|
□ Pipeline 页 `<section>` 带 `data-animate="pipeline"`,每 step 标 data-anim="step"
|
||||||
|
□ `grep -c 'data-anim' index.html` 数量 ≥ 页数 × 3(平均每页 3 个以上标记)
|
||||||
```
|
```
|
||||||
|
|
||||||
全勾完,才是合格的 PPT。
|
全勾完,才是合格的 PPT。
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
- [Icons 图标](#icons-图标)
|
- [Icons 图标](#icons-图标)
|
||||||
- [Ghost 巨型背景字](#ghost-巨型背景字)
|
- [Ghost 巨型背景字](#ghost-巨型背景字)
|
||||||
- [Highlight 荧光标记](#highlight-荧光标记)
|
- [Highlight 荧光标记](#highlight-荧光标记)
|
||||||
|
- [Motion 动效系统](#motion-动效系统)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -361,3 +362,72 @@
|
|||||||
在文字底部生成一条半透明高亮条。深色主题用亮条,浅色主题用暗条(CSS 已处理)。
|
在文字底部生成一条半透明高亮条。深色主题用亮条,浅色主题用暗条(CSS 已处理)。
|
||||||
|
|
||||||
**适合场景**:只对关键 1-3 个词使用,不要大面积用。
|
**适合场景**:只对关键 1-3 个词使用,不要大面积用。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Motion 动效系统
|
||||||
|
|
||||||
|
整套 deck 默认开启翻页入场动画,由 Motion One(vanilla 版 Framer Motion,约 4KB)驱动。
|
||||||
|
|
||||||
|
### 加载方式
|
||||||
|
|
||||||
|
`assets/template.html` 底部的 module script 会先尝试**本地** `assets/motion.min.js`,失败则回落到 **jsdelivr CDN**,两者都失败则强制把所有带 `data-anim` 的元素设为 `opacity:1`—— 内容永远可读,演示不依赖网络。
|
||||||
|
|
||||||
|
```js
|
||||||
|
// template 里的核心加载器(不用改)
|
||||||
|
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) {
|
||||||
|
document.querySelectorAll('[data-anim]').forEach(el=>{el.style.opacity='1';el.style.transform='none'});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 数据属性驱动
|
||||||
|
|
||||||
|
你只需要在 HTML 里加两种属性:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- 1. 在 section 上选 recipe(可选,默认 cascade / hero 自动) -->
|
||||||
|
<section class="slide light" data-animate="quote">
|
||||||
|
|
||||||
|
<!-- 2. 在需要入场的元素上加 data-anim(可选值:left/right/line/step/divider) -->
|
||||||
|
<h1 class="h-xl" data-anim>大标题</h1>
|
||||||
|
<div class="stat-card" data-anim>...</div>
|
||||||
|
<div data-anim="left">左列内容</div>
|
||||||
|
<span data-anim="line" style="display:block">引用第一行</span>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5 种 recipe 一览
|
||||||
|
|
||||||
|
| recipe | 触发方式 | 行为 | 代表布局 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `cascade`(默认) | 不加 `data-animate` 即为此值 | 所有 `data-anim` 逐个 stagger 淡入,75ms/step | Layout 3 / 4 / 5 / 10 |
|
||||||
|
| `hero` | `.hero` slide 自动用此值 | 慢节奏 stagger,仪式感更强,160ms/step | Layout 1 / 2 / 7 |
|
||||||
|
| `quote` | `data-animate="quote"` | 其他元素先出,`data-anim="line"` 的行 550ms 间隔逐句揭示 | Layout 8 |
|
||||||
|
| `directional` | `data-animate="directional"` | `data-anim="left"` 从左滑入 → divider → `data-anim="right"` 从右滑入 | Layout 9 |
|
||||||
|
| `pipeline` | `data-animate="pipeline"` | 翻到此页 step 保持 15% 透明;按 →/空格/滚轮逐个点亮,最后一步才放行翻页 | Layout 6 |
|
||||||
|
|
||||||
|
### 给 slide 选 recipe 的决策树
|
||||||
|
|
||||||
|
1. **它是 `.hero` slide 吗?** → 不用加 `data-animate`,自动用 `hero`
|
||||||
|
2. **它是大引用金句页?** → `data-animate="quote"`,每句用 `<span data-anim="line" style="display:block">`
|
||||||
|
3. **它是左右对比 Before/After?** → `data-animate="directional"`,左列 `data-anim="left"`、右列 `data-anim="right"`
|
||||||
|
4. **它是流水线分步讲解?** → `data-animate="pipeline"`,每步 `data-anim="step"`
|
||||||
|
5. **其他所有正文页** → 什么也不加,自动用 `cascade`
|
||||||
|
|
||||||
|
### 什么元素该加 `data-anim`?
|
||||||
|
|
||||||
|
- ✅ 每一层有独立语义的块:kicker / h1 / h-xl / lead / callout / stat-card / figure / tag / rowline
|
||||||
|
- ✅ 多列结构里每一列,让它们逐列淡入而不是一起
|
||||||
|
- ❌ 不要在容器(`.grid-6` / `.frame`)上加,只加给叶子元素
|
||||||
|
- ❌ 不要在每个 `<li>` 上加,一般在 `<ul>` 层加就够
|
||||||
|
- ❌ 如果某页不想要任何动画(比如过渡页),整页不加 `data-anim` 即可 — Motion One 只对带标记的元素生效
|
||||||
|
|
||||||
|
### 常见问题
|
||||||
|
|
||||||
|
- **图片闪一下再出现?** 这是预期行为,翻页中段(450ms 时)触发动画
|
||||||
|
- **Pipeline 页卡住翻不下页?** 正确的,按 → 一步一步点亮 step,全部点亮后再按 → 才翻页
|
||||||
|
- **内容静态时也不显示?** 检查 motion.min.js 是否在 `assets/` 下;或者浏览器控制台看错误信息
|
||||||
+80
-60
@@ -45,6 +45,24 @@ layouts.md 使用的所有类(`h-hero` / `h-xl` / `h-sub` / `h-md` / `lead` /
|
|||||||
- 主题节奏(每页用 light / dark / hero light / hero dark 哪一个)在下文"主题节奏规划"一节有硬规则,生成前必读
|
- 主题节奏(每页用 light / dark / hero light / hero dark 哪一个)在下文"主题节奏规划"一节有硬规则,生成前必读
|
||||||
- 两件事都要在挑布局之前决定,避免返工
|
- 两件事都要在挑布局之前决定,避免返工
|
||||||
|
|
||||||
|
### E. 动效系统(默认开启 · Motion One 驱动)
|
||||||
|
|
||||||
|
**核心机制**:template.html 底部的 module script 会在翻页时触发入场动画。所有带 `data-anim` 的元素初始不可见,翻到当前页时由 Motion One 逐个淡入。
|
||||||
|
|
||||||
|
**动效策略**:在 `<section>` 上加 `data-animate="<recipe>"` 选择动画风格;每个需要入场动画的元素加 `data-anim`(可选附值,如 `left` / `right` / `line` / `step`)。
|
||||||
|
|
||||||
|
| recipe | 用法 | 适合布局 |
|
||||||
|
|---|---|---|
|
||||||
|
| 默认(cascade) | 什么也不加,自动级联淡入 | 大部分正文页(Layout 3 / 4 / 5 / 10) |
|
||||||
|
| `hero` | `.hero` 页自动启用,节奏更慢更仪式感 | Layout 1 / 2 / 7(所有 hero 页) |
|
||||||
|
| `quote` | 一句一句揭示,慢节奏(550ms stagger) | Layout 8 大引用 |
|
||||||
|
| `directional` | 左进 → 分割 → 右进,用于对比 | Layout 9 Before/After |
|
||||||
|
| `pipeline` | 手动推进,按 →/空格 一步步点亮 | Layout 6 流水线 |
|
||||||
|
|
||||||
|
**降级保底**:如果 motion.min.js 本地 + CDN 都加载失败,脚本会强制把所有 `data-anim` 元素设为 `opacity:1`,内容永远可读。
|
||||||
|
|
||||||
|
**不需要动效的页面**:如果某页想完全跳过动效,不加任何 `data-anim` 即可 —— Motion One 只对带标记的元素生效。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 0. 基础结构(所有 slide 都一样)
|
## 0. 基础结构(所有 slide 都一样)
|
||||||
@@ -133,13 +151,13 @@ layouts.md 使用的所有类(`h-hero` / `h-xl` / `h-sub` / `h-md` / `lead` /
|
|||||||
<div>Vol.01</div>
|
<div>Vol.01</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="frame" style="display:grid; gap:4vh; align-content:center; min-height:80vh">
|
<div class="frame" style="display:grid; gap:4vh; align-content:center; min-height:80vh">
|
||||||
<div class="kicker">私享会 · 李继刚</div>
|
<div class="kicker" data-anim>私享会 · 李继刚</div>
|
||||||
<h1 class="h-hero">一人公司</h1>
|
<h1 class="h-hero" data-anim>一人公司</h1>
|
||||||
<h2 class="h-sub">被 AI 折叠的组织</h2>
|
<h2 class="h-sub" data-anim>被 AI 折叠的组织</h2>
|
||||||
<p class="lead" style="max-width:60vw">
|
<p class="lead" style="max-width:60vw" data-anim>
|
||||||
一个 AI 创作者 —— 在 64 天里做了 11 万行代码、在 9 个平台上持续输出,生活节奏几乎没有被改变。
|
一个 AI 创作者 —— 在 64 天里做了 11 万行代码、在 9 个平台上持续输出,生活节奏几乎没有被改变。
|
||||||
</p>
|
</p>
|
||||||
<div class="meta-row">
|
<div class="meta-row" data-anim>
|
||||||
<span>歸藏 Guizang</span><span>·</span><span>独立创作者 / CodePilot 作者</span>
|
<span>歸藏 Guizang</span><span>·</span><span>独立创作者 / CodePilot 作者</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -167,9 +185,9 @@ layouts.md 使用的所有类(`h-hero` / `h-xl` / `h-sub` / `h-md` / `lead` /
|
|||||||
<div>Act I · 01 / 25</div>
|
<div>Act I · 01 / 25</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="frame" style="display:grid; gap:6vh; align-content:center; min-height:80vh">
|
<div class="frame" style="display:grid; gap:6vh; align-content:center; min-height:80vh">
|
||||||
<div class="kicker">Act I</div>
|
<div class="kicker" data-anim>Act I</div>
|
||||||
<h1 class="h-hero" style="font-size:8.5vw">硬数据</h1>
|
<h1 class="h-hero" style="font-size:8.5vw" data-anim>硬数据</h1>
|
||||||
<p class="lead" style="max-width:55vw">
|
<p class="lead" style="max-width:55vw" data-anim>
|
||||||
先看数字,再谈方法。
|
先看数字,再谈方法。
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -196,37 +214,37 @@ layouts.md 使用的所有类(`h-hero` / `h-xl` / `h-sub` / `h-md` / `lead` /
|
|||||||
<div>Act I / Dev · 02 / 25</div>
|
<div>Act I / Dev · 02 / 25</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="frame" style="padding-top:6vh">
|
<div class="frame" style="padding-top:6vh">
|
||||||
<div class="kicker">一个人,做了什么。</div>
|
<div class="kicker" data-anim>一个人,做了什么。</div>
|
||||||
<h2 class="h-xl">过去 64 天</h2>
|
<h2 class="h-xl" data-anim>过去 64 天</h2>
|
||||||
<p class="lead" style="margin-bottom:5vh">从 0 到开源 CodePilot。</p>
|
<p class="lead" style="margin-bottom:5vh" data-anim>从 0 到开源 CodePilot。</p>
|
||||||
|
|
||||||
<div class="grid-6" style="margin-top:6vh">
|
<div class="grid-6" style="margin-top:6vh">
|
||||||
<div class="stat-card">
|
<div class="stat-card" data-anim>
|
||||||
<div class="stat-label">Duration</div>
|
<div class="stat-label">Duration</div>
|
||||||
<div class="stat-nb">64 <span class="stat-unit">天</span></div>
|
<div class="stat-nb">64 <span class="stat-unit">天</span></div>
|
||||||
<div class="stat-note">从 0 到现在</div>
|
<div class="stat-note">从 0 到现在</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-card">
|
<div class="stat-card" data-anim>
|
||||||
<div class="stat-label">Lines of Code</div>
|
<div class="stat-label">Lines of Code</div>
|
||||||
<div class="stat-nb">110K+</div>
|
<div class="stat-nb">110K+</div>
|
||||||
<div class="stat-note">一行行写到 11 万+</div>
|
<div class="stat-note">一行行写到 11 万+</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-card">
|
<div class="stat-card" data-anim>
|
||||||
<div class="stat-label">GitHub Stars</div>
|
<div class="stat-label">GitHub Stars</div>
|
||||||
<div class="stat-nb">5,166</div>
|
<div class="stat-nb">5,166</div>
|
||||||
<div class="stat-note">一个开源仓库</div>
|
<div class="stat-note">一个开源仓库</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-card">
|
<div class="stat-card" data-anim>
|
||||||
<div class="stat-label">Downloads</div>
|
<div class="stat-label">Downloads</div>
|
||||||
<div class="stat-nb">41K+</div>
|
<div class="stat-nb">41K+</div>
|
||||||
<div class="stat-note">装到了几万台电脑里</div>
|
<div class="stat-note">装到了几万台电脑里</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-card">
|
<div class="stat-card" data-anim>
|
||||||
<div class="stat-label">AI Providers</div>
|
<div class="stat-label">AI Providers</div>
|
||||||
<div class="stat-nb">19</div>
|
<div class="stat-nb">19</div>
|
||||||
<div class="stat-note">跨平台接入</div>
|
<div class="stat-note">跨平台接入</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-card">
|
<div class="stat-card" data-anim>
|
||||||
<div class="stat-label">Commits</div>
|
<div class="stat-label">Commits</div>
|
||||||
<div class="stat-nb">608+</div>
|
<div class="stat-nb">608+</div>
|
||||||
<div class="stat-note">没有协作者</div>
|
<div class="stat-note">没有协作者</div>
|
||||||
@@ -260,22 +278,22 @@ layouts.md 使用的所有类(`h-hero` / `h-xl` / `h-sub` / `h-md` / `lead` /
|
|||||||
<!-- 左列:标题 + 正文 + callout,flex column 让 callout 贴列底 -->
|
<!-- 左列:标题 + 正文 + callout,flex column 让 callout 贴列底 -->
|
||||||
<div style="display:flex; flex-direction:column; justify-content:space-between; gap:3vh">
|
<div style="display:flex; flex-direction:column; justify-content:space-between; gap:3vh">
|
||||||
<div>
|
<div>
|
||||||
<div class="kicker">BUT</div>
|
<div class="kicker" data-anim>BUT</div>
|
||||||
<h2 class="h-xl" style="white-space:nowrap; font-size:7.2vw">
|
<h2 class="h-xl" style="white-space:nowrap; font-size:7.2vw" data-anim>
|
||||||
我不是程序员。
|
我不是程序员。
|
||||||
</h2>
|
</h2>
|
||||||
<p class="lead" style="margin-top:3vh">
|
<p class="lead" style="margin-top:3vh" data-anim>
|
||||||
大学毕业之后再也没写过一行代码。过去十年做的是 UI 设计和 AI 特效。
|
大学毕业之后再也没写过一行代码。过去十年做的是 UI 设计和 AI 特效。
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="callout">
|
<div class="callout" data-anim>
|
||||||
"这东西在三年前,<br>
|
"这东西在三年前,<br>
|
||||||
需要一个十人团队做一年。"
|
需要一个十人团队做一年。"
|
||||||
<div class="callout-src">— 一个观察者的判断</div>
|
<div class="callout-src">— 一个观察者的判断</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 右列:图片用标准 16/10 比例 + max-height,不要 align-self:end -->
|
<!-- 右列:图片用标准 16/10 比例 + max-height,不要 align-self:end -->
|
||||||
<figure class="frame-img" style="aspect-ratio:16/10; max-height:56vh">
|
<figure class="frame-img" style="aspect-ratio:16/10; max-height:56vh" data-anim>
|
||||||
<img src="images/codepilot.png" alt="CodePilot 产品截图">
|
<img src="images/codepilot.png" alt="CodePilot 产品截图">
|
||||||
<figcaption class="img-cap">CodePilot · 产品截图</figcaption>
|
<figcaption class="img-cap">CodePilot · 产品截图</figcaption>
|
||||||
</figure>
|
</figure>
|
||||||
@@ -304,31 +322,31 @@ layouts.md 使用的所有类(`h-hero` / `h-xl` / `h-sub` / `h-md` / `lead` /
|
|||||||
<div>Act I / Ops · 05 / 27</div>
|
<div>Act I / Ops · 05 / 27</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="frame" style="padding-top:5vh">
|
<div class="frame" style="padding-top:5vh">
|
||||||
<div class="kicker">Proof · 粉丝实证</div>
|
<div class="kicker" data-anim>Proof · 粉丝实证</div>
|
||||||
<h2 class="h-xl">10 个平台 · 6 张截图</h2>
|
<h2 class="h-xl" data-anim>10 个平台 · 6 张截图</h2>
|
||||||
|
|
||||||
<div class="grid-3-3" style="margin-top:4vh">
|
<div class="grid-3-3" style="margin-top:4vh">
|
||||||
<figure class="frame-img" style="height:26vh">
|
<figure class="frame-img" style="height:26vh" data-anim>
|
||||||
<img src="images/weibo.png" alt="微博 289K">
|
<img src="images/weibo.png" alt="微博 289K">
|
||||||
<figcaption class="img-cap">微博 · 289K</figcaption>
|
<figcaption class="img-cap">微博 · 289K</figcaption>
|
||||||
</figure>
|
</figure>
|
||||||
<figure class="frame-img" style="height:26vh">
|
<figure class="frame-img" style="height:26vh" data-anim>
|
||||||
<img src="images/twitter.png" alt="推特 137K">
|
<img src="images/twitter.png" alt="推特 137K">
|
||||||
<figcaption class="img-cap">推特 · 137K</figcaption>
|
<figcaption class="img-cap">推特 · 137K</figcaption>
|
||||||
</figure>
|
</figure>
|
||||||
<figure class="frame-img" style="height:26vh">
|
<figure class="frame-img" style="height:26vh" data-anim>
|
||||||
<img src="images/wechat.png" alt="公众号 96K">
|
<img src="images/wechat.png" alt="公众号 96K">
|
||||||
<figcaption class="img-cap">公众号 · 96K</figcaption>
|
<figcaption class="img-cap">公众号 · 96K</figcaption>
|
||||||
</figure>
|
</figure>
|
||||||
<figure class="frame-img" style="height:26vh">
|
<figure class="frame-img" style="height:26vh" data-anim>
|
||||||
<img src="images/jike.png" alt="即刻 26K">
|
<img src="images/jike.png" alt="即刻 26K">
|
||||||
<figcaption class="img-cap">即刻 · 26K</figcaption>
|
<figcaption class="img-cap">即刻 · 26K</figcaption>
|
||||||
</figure>
|
</figure>
|
||||||
<figure class="frame-img" style="height:26vh">
|
<figure class="frame-img" style="height:26vh" data-anim>
|
||||||
<img src="images/xhs.png" alt="小红书 19K">
|
<img src="images/xhs.png" alt="小红书 19K">
|
||||||
<figcaption class="img-cap">小红书 · 19K</figcaption>
|
<figcaption class="img-cap">小红书 · 19K</figcaption>
|
||||||
</figure>
|
</figure>
|
||||||
<figure class="frame-img" style="height:26vh">
|
<figure class="frame-img" style="height:26vh" data-anim>
|
||||||
<img src="images/douyin.png" alt="抖音 10K">
|
<img src="images/douyin.png" alt="抖音 10K">
|
||||||
<figcaption class="img-cap">抖音 · 10K</figcaption>
|
<figcaption class="img-cap">抖音 · 10K</figcaption>
|
||||||
</figure>
|
</figure>
|
||||||
@@ -351,7 +369,7 @@ layouts.md 使用的所有类(`h-hero` / `h-xl` / `h-sub` / `h-md` / `lead` /
|
|||||||
## Layout 6: 两列流水线(Pipeline)
|
## Layout 6: 两列流水线(Pipeline)
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<section class="slide light">
|
<section class="slide light" data-animate="pipeline">
|
||||||
<div class="chrome">
|
<div class="chrome">
|
||||||
<div>我的工作流 · Workflow</div>
|
<div>我的工作流 · Workflow</div>
|
||||||
<div>Act II · 15 / 27</div>
|
<div>Act II · 15 / 27</div>
|
||||||
@@ -364,27 +382,27 @@ layouts.md 使用的所有类(`h-hero` / `h-xl` / `h-sub` / `h-md` / `lead` /
|
|||||||
<div class="pipeline-section">
|
<div class="pipeline-section">
|
||||||
<div class="pipeline-label">文本侧 · Text Pipeline</div>
|
<div class="pipeline-label">文本侧 · Text Pipeline</div>
|
||||||
<div class="pipeline">
|
<div class="pipeline">
|
||||||
<div class="step">
|
<div class="step" data-anim="step">
|
||||||
<div class="step-nb">01</div>
|
<div class="step-nb">01</div>
|
||||||
<div class="step-title">Draft</div>
|
<div class="step-title">Draft</div>
|
||||||
<div class="step-desc">AI 帮我起草初稿</div>
|
<div class="step-desc">AI 帮我起草初稿</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="step">
|
<div class="step" data-anim="step">
|
||||||
<div class="step-nb">02</div>
|
<div class="step-nb">02</div>
|
||||||
<div class="step-title">Polish</div>
|
<div class="step-title">Polish</div>
|
||||||
<div class="step-desc">AI 润色去 AI 味</div>
|
<div class="step-desc">AI 润色去 AI 味</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="step">
|
<div class="step" data-anim="step">
|
||||||
<div class="step-nb">03</div>
|
<div class="step-nb">03</div>
|
||||||
<div class="step-title">Morph</div>
|
<div class="step-title">Morph</div>
|
||||||
<div class="step-desc">AI 变形成推特 / 小红书</div>
|
<div class="step-desc">AI 变形成推特 / 小红书</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="step">
|
<div class="step" data-anim="step">
|
||||||
<div class="step-nb">04</div>
|
<div class="step-nb">04</div>
|
||||||
<div class="step-title">Illustrate</div>
|
<div class="step-title">Illustrate</div>
|
||||||
<div class="step-desc">AI 生成信息图</div>
|
<div class="step-desc">AI 生成信息图</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="step">
|
<div class="step" data-anim="step">
|
||||||
<div class="step-nb">05</div>
|
<div class="step-nb">05</div>
|
||||||
<div class="step-title">Distribute</div>
|
<div class="step-title">Distribute</div>
|
||||||
<div class="step-desc">一键分发 9 平台</div>
|
<div class="step-desc">一键分发 9 平台</div>
|
||||||
@@ -396,17 +414,17 @@ layouts.md 使用的所有类(`h-hero` / `h-xl` / `h-sub` / `h-md` / `lead` /
|
|||||||
<div class="pipeline-section">
|
<div class="pipeline-section">
|
||||||
<div class="pipeline-label">视觉 · 视频侧 · Video Pipeline</div>
|
<div class="pipeline-label">视觉 · 视频侧 · Video Pipeline</div>
|
||||||
<div class="pipeline">
|
<div class="pipeline">
|
||||||
<div class="step">
|
<div class="step" data-anim="step">
|
||||||
<div class="step-nb">06</div>
|
<div class="step-nb">06</div>
|
||||||
<div class="step-title">Cut</div>
|
<div class="step-title">Cut</div>
|
||||||
<div class="step-desc">AI 帮我剪辑</div>
|
<div class="step-desc">AI 帮我剪辑</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="step">
|
<div class="step" data-anim="step">
|
||||||
<div class="step-nb">07</div>
|
<div class="step-nb">07</div>
|
||||||
<div class="step-title">Wrap</div>
|
<div class="step-title">Wrap</div>
|
||||||
<div class="step-desc">AI 帮我包装</div>
|
<div class="step-desc">AI 帮我包装</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="step">
|
<div class="step" data-anim="step">
|
||||||
<div class="step-nb">08</div>
|
<div class="step-nb">08</div>
|
||||||
<div class="step-title">Cover</div>
|
<div class="step-title">Cover</div>
|
||||||
<div class="step-desc">AI 生成封面</div>
|
<div class="step-desc">AI 生成封面</div>
|
||||||
@@ -426,6 +444,7 @@ layouts.md 使用的所有类(`h-hero` / `h-xl` / `h-sub` / `h-md` / `lead` /
|
|||||||
- 两组之间用 3.6vh 的间距 + 顶部细分隔线(已在 CSS 中预设)
|
- 两组之间用 3.6vh 的间距 + 顶部细分隔线(已在 CSS 中预设)
|
||||||
- 每个 step 是固定的 nb → title → desc 结构
|
- 每个 step 是固定的 nb → title → desc 结构
|
||||||
- 步骤数不限但单行最好 ≤5 个,否则换到第二 pipeline
|
- 步骤数不限但单行最好 ≤5 个,否则换到第二 pipeline
|
||||||
|
- **动效**:`<section>` 加 `data-animate="pipeline"`,每个 `.step` 加 `data-anim="step"`。翻到此页时步骤默认 `opacity:.15`,按 →/空格/滚轮下滑时一次点亮一个 step;**所有 step 点亮完才会翻到下一页**,可制造演讲互动感
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -438,13 +457,13 @@ layouts.md 使用的所有类(`h-hero` / `h-xl` / `h-sub` / `h-md` / `lead` /
|
|||||||
<div>24 / 27</div>
|
<div>24 / 27</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="frame" style="display:grid; gap:8vh; align-content:center; min-height:80vh">
|
<div class="frame" style="display:grid; gap:8vh; align-content:center; min-height:80vh">
|
||||||
<div class="kicker">The Question</div>
|
<div class="kicker" data-anim>The Question</div>
|
||||||
<h1 class="h-hero" style="font-size:7vw; line-height:1.15">
|
<h1 class="h-hero" style="font-size:7vw; line-height:1.15">
|
||||||
你的公司里,<br>
|
<span data-anim style="display:block">你的公司里,</span>
|
||||||
哪些岗位本来就<br>
|
<span data-anim style="display:block">哪些岗位本来就</span>
|
||||||
不该由人来做?
|
<span data-anim style="display:block">不该由人来做?</span>
|
||||||
</h1>
|
</h1>
|
||||||
<p class="lead" style="max-width:50vw">
|
<p class="lead" style="max-width:50vw" data-anim>
|
||||||
这个问题,不是技术问题,是架构问题。
|
这个问题,不是技术问题,是架构问题。
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -466,21 +485,22 @@ layouts.md 使用的所有类(`h-hero` / `h-xl` / `h-sub` / `h-md` / `lead` /
|
|||||||
## Layout 8: 大引用页(Big Quote · 衬线金句)
|
## Layout 8: 大引用页(Big Quote · 衬线金句)
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<section class="slide light">
|
<section class="slide light" data-animate="quote">
|
||||||
<div class="chrome">
|
<div class="chrome">
|
||||||
<div>The Takeaway · 核心金句</div>
|
<div>The Takeaway · 核心金句</div>
|
||||||
<div>18 / 25</div>
|
<div>18 / 25</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="frame" style="display:grid; gap:5vh; align-content:center; min-height:80vh">
|
<div class="frame" style="display:grid; gap:5vh; align-content:center; min-height:80vh">
|
||||||
<div class="kicker">Quote · 金句</div>
|
<div class="kicker" data-anim>Quote · 金句</div>
|
||||||
<blockquote style="font-family:var(--serif-zh); font-weight:700; font-size:5.8vw; line-height:1.2; letter-spacing:-.01em; max-width:72vw">
|
<blockquote style="font-family:var(--serif-zh); font-weight:700; font-size:5.8vw; line-height:1.2; letter-spacing:-.01em; max-width:72vw">
|
||||||
"没有交接,<br>所有人都在构建。"
|
<span data-anim="line" style="display:block">"没有交接,</span>
|
||||||
|
<span data-anim="line" style="display:block">所有人都在构建。"</span>
|
||||||
</blockquote>
|
</blockquote>
|
||||||
<p class="lead" style="max-width:55vw; opacity:.65">
|
<p class="lead" style="max-width:55vw; opacity:.65" data-anim>
|
||||||
Without the handoff, everyone builds.<br>
|
Without the handoff, everyone builds.<br>
|
||||||
And that makes all the difference.
|
And that makes all the difference.
|
||||||
</p>
|
</p>
|
||||||
<div class="meta-row">
|
<div class="meta-row" data-anim>
|
||||||
<span>— Luke Wroblewski</span><span>·</span><span>2026.04.16</span>
|
<span>— Luke Wroblewski</span><span>·</span><span>2026.04.16</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -502,18 +522,18 @@ layouts.md 使用的所有类(`h-hero` / `h-xl` / `h-sub` / `h-md` / `lead` /
|
|||||||
## Layout 9: 并列对比(A vs B · 旧 vs 新)
|
## Layout 9: 并列对比(A vs B · 旧 vs 新)
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<section class="slide light">
|
<section class="slide light" data-animate="directional">
|
||||||
<div class="chrome">
|
<div class="chrome">
|
||||||
<div>旧 vs 新 · The Shift</div>
|
<div>旧 vs 新 · The Shift</div>
|
||||||
<div>12 / 25</div>
|
<div>12 / 25</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="frame" style="padding-top:5vh">
|
<div class="frame" style="padding-top:5vh">
|
||||||
<div class="kicker">Before / After · 范式转变</div>
|
<div class="kicker" data-anim>Before / After · 范式转变</div>
|
||||||
<h2 class="h-xl" style="margin-bottom:4vh">从交接到共建</h2>
|
<h2 class="h-xl" style="margin-bottom:4vh" data-anim>从交接到共建</h2>
|
||||||
|
|
||||||
<div class="grid-2-6-6" style="gap:5vw 4vh">
|
<div class="grid-2-6-6" style="gap:5vw 4vh">
|
||||||
<!-- 左列:旧 -->
|
<!-- 左列:旧 -->
|
||||||
<div style="padding:3vh 2vw; border-left:3px solid currentColor; opacity:.55">
|
<div data-anim="left" style="padding:3vh 2vw; border-left:3px solid currentColor; opacity:.55">
|
||||||
<div class="kicker" style="opacity:.9">Before · 旧模式</div>
|
<div class="kicker" style="opacity:.9">Before · 旧模式</div>
|
||||||
<h3 class="h-md" style="margin-top:2vh">设计 → 开发 → 交接</h3>
|
<h3 class="h-md" style="margin-top:2vh">设计 → 开发 → 交接</h3>
|
||||||
<ul style="margin-top:3vh; padding-left:1.2em; display:flex; flex-direction:column; gap:1.4vh; font-family:var(--sans-zh); font-size:max(14px,1.1vw); line-height:1.55">
|
<ul style="margin-top:3vh; padding-left:1.2em; display:flex; flex-direction:column; gap:1.4vh; font-family:var(--sans-zh); font-size:max(14px,1.1vw); line-height:1.55">
|
||||||
@@ -524,7 +544,7 @@ layouts.md 使用的所有类(`h-hero` / `h-xl` / `h-sub` / `h-md` / `lead` /
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<!-- 右列:新 -->
|
<!-- 右列:新 -->
|
||||||
<div style="padding:3vh 2vw; border-left:3px solid currentColor">
|
<div data-anim="right" style="padding:3vh 2vw; border-left:3px solid currentColor">
|
||||||
<div class="kicker" style="opacity:.9">After · 新模式</div>
|
<div class="kicker" style="opacity:.9">After · 新模式</div>
|
||||||
<h3 class="h-md" style="margin-top:2vh">同工具 · 并行 · 共建</h3>
|
<h3 class="h-md" style="margin-top:2vh">同工具 · 并行 · 共建</h3>
|
||||||
<ul style="margin-top:3vh; padding-left:1.2em; display:flex; flex-direction:column; gap:1.4vh; font-family:var(--sans-zh); font-size:max(14px,1.1vw); line-height:1.55">
|
<ul style="margin-top:3vh; padding-left:1.2em; display:flex; flex-direction:column; gap:1.4vh; font-family:var(--sans-zh); font-size:max(14px,1.1vw); line-height:1.55">
|
||||||
@@ -562,24 +582,24 @@ layouts.md 使用的所有类(`h-hero` / `h-xl` / `h-sub` / `h-md` / `lead` /
|
|||||||
<div class="frame grid-2-8-4" style="padding-top:6vh">
|
<div class="frame grid-2-8-4" style="padding-top:6vh">
|
||||||
<!-- 左列:大段正文 + 引用 -->
|
<!-- 左列:大段正文 + 引用 -->
|
||||||
<div>
|
<div>
|
||||||
<div class="kicker">Phase 01 · 设计阶段</div>
|
<div class="kicker" data-anim>Phase 01 · 设计阶段</div>
|
||||||
<h2 class="h-xl" style="margin-top:1vh; margin-bottom:3vh">设计先行 · 2 周</h2>
|
<h2 class="h-xl" style="margin-top:1vh; margin-bottom:3vh" data-anim>设计先行 · 2 周</h2>
|
||||||
|
|
||||||
<p class="lead" style="margin-bottom:3vh">
|
<p class="lead" style="margin-bottom:3vh" data-anim>
|
||||||
在 Figma 中完成视觉探索与设计系统,网格 / 排版 / 颜色变量 / 可复用组件,桌面和移动端稿件几轮反馈迭代。
|
在 Figma 中完成视觉探索与设计系统,网格 / 排版 / 颜色变量 / 可复用组件,桌面和移动端稿件几轮反馈迭代。
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p style="font-family:var(--sans-zh); font-size:max(14px,1.15vw); line-height:1.75; opacity:.78; margin-bottom:2.4vh">
|
<p data-anim style="font-family:var(--sans-zh); font-size:max(14px,1.15vw); line-height:1.75; opacity:.78; margin-bottom:2.4vh">
|
||||||
两周之内,视觉风格、粗略结构、方向性内容全部稳定。这是扎实的传统设计流程——在这里还没什么新鲜事。
|
两周之内,视觉风格、粗略结构、方向性内容全部稳定。这是扎实的传统设计流程——在这里还没什么新鲜事。
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="callout" style="margin-top:3vh">
|
<div class="callout" style="margin-top:3vh" data-anim>
|
||||||
"This phase was pretty standard.<br>Just a solid Web design process."
|
"This phase was pretty standard.<br>Just a solid Web design process."
|
||||||
<div class="callout-src">— Luke Wroblewski</div>
|
<div class="callout-src">— Luke Wroblewski</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 右列:辅助图 · 竖版或方形 -->
|
<!-- 右列:辅助图 · 竖版或方形 -->
|
||||||
<figure class="frame-img" style="aspect-ratio:3/4; max-height:60vh">
|
<figure class="frame-img" style="aspect-ratio:3/4; max-height:60vh" data-anim>
|
||||||
<img src="images/figma.png" alt="Figma design system">
|
<img src="images/figma.png" alt="Figma design system">
|
||||||
<figcaption class="img-cap">Figma · Design System</figcaption>
|
<figcaption class="img-cap">Figma · Design System</figcaption>
|
||||||
</figure>
|
</figure>
|
||||||
|
|||||||
Reference in new issue
Block a user