stagecraft 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENT.md +792 -0
- package/LICENSE +21 -0
- package/README.md +210 -0
- package/bin/cli.js +51 -0
- package/bin/export.js +137 -0
- package/bin/init.js +52 -0
- package/bin/lib/edit-ops.js +405 -0
- package/bin/serve.js +278 -0
- package/dist/stagecraft.bundle.css +4443 -0
- package/dist/stagecraft.bundle.js +7621 -0
- package/dist/themes/brand.bundle.css +5262 -0
- package/dist/themes/neon.bundle.css +5289 -0
- package/dist/themes/paper.bundle.css +5276 -0
- package/dist/themes/phosphor.bundle.css +4443 -0
- package/dist/themes/shopware.bundle.css +5850 -0
- package/examples/closing-card.js +74 -0
- package/examples/orchestration-graph.js +156 -0
- package/examples/terminal-log.js +109 -0
- package/examples/token-stream.js +96 -0
- package/examples/whoami.js +90 -0
- package/package.json +41 -0
- package/src/components/activity-list.js +75 -0
- package/src/components/agenda.js +79 -0
- package/src/components/bar-chart.js +162 -0
- package/src/components/before-after.js +135 -0
- package/src/components/bento.js +73 -0
- package/src/components/big-number.js +87 -0
- package/src/components/callout.js +75 -0
- package/src/components/checklist.js +81 -0
- package/src/components/code-block.js +141 -0
- package/src/components/code-diff.js +98 -0
- package/src/components/compare.js +85 -0
- package/src/components/counter.js +80 -0
- package/src/components/cta.js +69 -0
- package/src/components/cycle.js +146 -0
- package/src/components/definition.js +96 -0
- package/src/components/donut-chart.js +179 -0
- package/src/components/full-image.js +82 -0
- package/src/components/funnel.js +111 -0
- package/src/components/gauge.js +147 -0
- package/src/components/heatmap.js +141 -0
- package/src/components/image-grid.js +80 -0
- package/src/components/image-text.js +96 -0
- package/src/components/kinetic-text.js +72 -0
- package/src/components/kpi.js +106 -0
- package/src/components/line-chart.js +215 -0
- package/src/components/manifesto.js +104 -0
- package/src/components/marquee.js +63 -0
- package/src/components/matrix2x2.js +151 -0
- package/src/components/pillars.js +80 -0
- package/src/components/pricing.js +90 -0
- package/src/components/process-flow.js +133 -0
- package/src/components/progress.js +136 -0
- package/src/components/punchline.js +82 -0
- package/src/components/pyramid.js +107 -0
- package/src/components/qanda.js +60 -0
- package/src/components/quote.js +70 -0
- package/src/components/roadmap.js +130 -0
- package/src/components/section-card.js +45 -0
- package/src/components/shift-arrow.js +41 -0
- package/src/components/spark-line.js +147 -0
- package/src/components/spotlight.js +85 -0
- package/src/components/statement.js +106 -0
- package/src/components/stats.js +91 -0
- package/src/components/steps.js +83 -0
- package/src/components/swot.js +110 -0
- package/src/components/team-grid.js +87 -0
- package/src/components/testimonial.js +99 -0
- package/src/components/timeline.js +91 -0
- package/src/components/tip.js +63 -0
- package/src/components/venn.js +198 -0
- package/src/edit-mode.js +1256 -0
- package/src/engine.js +823 -0
- package/src/helpers.js +169 -0
- package/src/transitions.js +101 -0
- package/starter/index.html +40 -0
- package/starter/slides/00-title.js +12 -0
- package/starter/stagecraft.config.js +8 -0
- package/themes/brand/base.css +4 -0
- package/themes/brand/components-business.css +173 -0
- package/themes/brand/components-chart.css +65 -0
- package/themes/brand/components-content.css +126 -0
- package/themes/brand/components-data.css +162 -0
- package/themes/brand/components-diagram.css +115 -0
- package/themes/brand/components-layout.css +112 -0
- package/themes/brand/components.css +46 -0
- package/themes/brand/manifest.json +20 -0
- package/themes/brand/tokens.css +20 -0
- package/themes/brand/transitions.css +4 -0
- package/themes/neon/base.css +10 -0
- package/themes/neon/components-business.css +189 -0
- package/themes/neon/components-chart.css +70 -0
- package/themes/neon/components-content.css +112 -0
- package/themes/neon/components-data.css +160 -0
- package/themes/neon/components-diagram.css +109 -0
- package/themes/neon/components-layout.css +87 -0
- package/themes/neon/components.css +87 -0
- package/themes/neon/manifest.json +21 -0
- package/themes/neon/tokens.css +17 -0
- package/themes/neon/transitions.css +13 -0
- package/themes/paper/base.css +9 -0
- package/themes/paper/components-business.css +196 -0
- package/themes/paper/components-chart.css +74 -0
- package/themes/paper/components-content.css +108 -0
- package/themes/paper/components-data.css +168 -0
- package/themes/paper/components-diagram.css +89 -0
- package/themes/paper/components-layout.css +105 -0
- package/themes/paper/components.css +60 -0
- package/themes/paper/manifest.json +10 -0
- package/themes/paper/tokens.css +21 -0
- package/themes/paper/transitions.css +11 -0
- package/themes/phosphor/base.css +511 -0
- package/themes/phosphor/components-business.css +818 -0
- package/themes/phosphor/components-chart.css +415 -0
- package/themes/phosphor/components-content.css +530 -0
- package/themes/phosphor/components-data.css +824 -0
- package/themes/phosphor/components-diagram.css +427 -0
- package/themes/phosphor/components-layout.css +450 -0
- package/themes/phosphor/components.css +223 -0
- package/themes/phosphor/manifest.json +11 -0
- package/themes/phosphor/tokens.css +17 -0
- package/themes/phosphor/transitions.css +213 -0
- package/themes/shopware/base.css +94 -0
- package/themes/shopware/components-business.css +344 -0
- package/themes/shopware/components-chart.css +121 -0
- package/themes/shopware/components-content.css +169 -0
- package/themes/shopware/components-data.css +219 -0
- package/themes/shopware/components-diagram.css +129 -0
- package/themes/shopware/components-layout.css +166 -0
- package/themes/shopware/components.css +83 -0
- package/themes/shopware/manifest.json +21 -0
- package/themes/shopware/tokens.css +68 -0
- package/themes/shopware/transitions.css +22 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Stage.Agenda — vertical schedule with time on left, label on right.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* Stage.register(Stage.Agenda({
|
|
8
|
+
* section: 63,
|
|
9
|
+
* title: '63 · Today',
|
|
10
|
+
* items: [
|
|
11
|
+
* { time: '09:00', label: 'Welcome', duration: '15 min', icon: 'waving_hand' },
|
|
12
|
+
* { time: '09:15', label: 'Keynote', duration: '45 min', icon: 'campaign' },
|
|
13
|
+
* { time: '10:00', label: 'Coffee', duration: '20 min', icon: 'coffee' }
|
|
14
|
+
* ],
|
|
15
|
+
* reveal: 'staggered' // 'instant' | 'staggered' | 'per-click'
|
|
16
|
+
* }));
|
|
17
|
+
*
|
|
18
|
+
* Edit paths: items[i].time / .label / .duration / .icon
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
(function (root) {
|
|
22
|
+
const Stage = root.Stage = root.Stage || {};
|
|
23
|
+
|
|
24
|
+
Stage.Agenda = function (opts) {
|
|
25
|
+
const items = opts.items || [];
|
|
26
|
+
const reveal = opts.reveal || 'instant';
|
|
27
|
+
|
|
28
|
+
const slide = {
|
|
29
|
+
section: opts.section,
|
|
30
|
+
title: opts.title,
|
|
31
|
+
transition: opts.transition,
|
|
32
|
+
render(el) {
|
|
33
|
+
el.innerHTML = `
|
|
34
|
+
<div class="agenda" data-stage-key="Agenda">
|
|
35
|
+
<div class="agenda-rail"></div>
|
|
36
|
+
${items.map((it, i) => `
|
|
37
|
+
<div class="agenda-item" data-step="${i + 1}" data-stage-key="Agenda/item[${i}]">
|
|
38
|
+
${it.time ? `<div class="agenda-time" data-stage-edit="items[${i}].time">${escape(it.time)}</div>` : `<div class="agenda-time"></div>`}
|
|
39
|
+
<div class="agenda-dot">
|
|
40
|
+
${it.icon ? `<span class="material-symbols-outlined" data-stage-edit="items[${i}].icon">${escape(it.icon)}</span>` : ''}
|
|
41
|
+
</div>
|
|
42
|
+
<div class="agenda-body">
|
|
43
|
+
<div class="agenda-label" data-stage-edit="items[${i}].label">${escape(it.label || '')}</div>
|
|
44
|
+
${it.duration ? `<div class="agenda-duration" data-stage-edit="items[${i}].duration">${escape(it.duration)}</div>` : ''}
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
`).join('')}
|
|
48
|
+
</div>
|
|
49
|
+
`;
|
|
50
|
+
if (reveal === 'instant') {
|
|
51
|
+
el.querySelectorAll('.agenda-item').forEach(n => n.classList.add('in'));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
if (reveal === 'staggered') {
|
|
57
|
+
slide.init = function (el) {
|
|
58
|
+
return Stage.staggerIn(el.querySelectorAll('.agenda-item'), 140, 180);
|
|
59
|
+
};
|
|
60
|
+
slide.replay = function (el) {
|
|
61
|
+
el.querySelectorAll('.agenda-item').forEach(n => n.classList.remove('in'));
|
|
62
|
+
return this.init(el);
|
|
63
|
+
};
|
|
64
|
+
} else if (reveal === 'per-click') {
|
|
65
|
+
slide.steps = items.length;
|
|
66
|
+
slide.onStep = function (el, step) {
|
|
67
|
+
el.querySelectorAll('.agenda-item').forEach(n => {
|
|
68
|
+
n.classList.toggle('in', Number(n.dataset.step) <= step);
|
|
69
|
+
});
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return slide;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
function escape(s) {
|
|
77
|
+
return String(s).replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
|
78
|
+
}
|
|
79
|
+
})(typeof window !== 'undefined' ? window : globalThis);
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Stage.BarChart — simple horizontal or vertical bar chart.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* Stage.register(Stage.BarChart({
|
|
8
|
+
* section: 4,
|
|
9
|
+
* title: '04 · where the hours go',
|
|
10
|
+
* orientation: 'horizontal', // default
|
|
11
|
+
* bars: [
|
|
12
|
+
* { label: 'Coding', value: 6, color: 'accent' },
|
|
13
|
+
* { label: 'Meetings', value: 3, color: 'amber' },
|
|
14
|
+
* { label: 'Slack', value: 2, color: 'blue' },
|
|
15
|
+
* { label: 'Thinking', value: 1, color: 'dim' }
|
|
16
|
+
* ],
|
|
17
|
+
* unit: ' h',
|
|
18
|
+
* reveal: 'animated' // 'instant' | 'animated' | 'per-click'
|
|
19
|
+
* }));
|
|
20
|
+
*
|
|
21
|
+
* `maxValue` defaults to max(values) * 1.1.
|
|
22
|
+
* Animation: CSS transitions on width/height when `.fill` class lands.
|
|
23
|
+
*
|
|
24
|
+
* Layer-3 (inline edit): bars[i].label, bars[i].value, unit, title.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
(function (root) {
|
|
28
|
+
const Stage = root.Stage = root.Stage || {};
|
|
29
|
+
|
|
30
|
+
Stage.BarChart = function (opts) {
|
|
31
|
+
const bars = opts.bars || [];
|
|
32
|
+
const orientation = opts.orientation === 'vertical' ? 'vertical' : 'horizontal';
|
|
33
|
+
const unit = opts.unit || '';
|
|
34
|
+
const reveal = opts.reveal || 'instant';
|
|
35
|
+
const values = bars.map(b => Number(b.value) || 0);
|
|
36
|
+
const computedMax = values.length ? Math.max(...values) : 1;
|
|
37
|
+
const maxValue = Number(opts.maxValue) > 0
|
|
38
|
+
? Number(opts.maxValue)
|
|
39
|
+
: (computedMax > 0 ? computedMax * 1.1 : 1);
|
|
40
|
+
|
|
41
|
+
function pct(v) {
|
|
42
|
+
const p = (Number(v) || 0) / maxValue * 100;
|
|
43
|
+
return Math.max(0, Math.min(100, p));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function formatValue(v) {
|
|
47
|
+
// Keep simple — leave numeric formatting to the caller via value/unit.
|
|
48
|
+
return `${v}${unit}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const slide = {
|
|
52
|
+
section: opts.section,
|
|
53
|
+
title: opts.title,
|
|
54
|
+
transition: opts.transition,
|
|
55
|
+
render(el) {
|
|
56
|
+
const sizeProp = orientation === 'vertical' ? 'height' : 'width';
|
|
57
|
+
const rows = bars.map((b, i) => {
|
|
58
|
+
const colorCls = b.color ? ` ${b.color}` : '';
|
|
59
|
+
const p = pct(b.value);
|
|
60
|
+
return `
|
|
61
|
+
<div class="bar-row${colorCls}"
|
|
62
|
+
data-step="${i + 1}"
|
|
63
|
+
data-pct="${p}"
|
|
64
|
+
data-stage-key="BarChart/bar[${i}]">
|
|
65
|
+
<div class="bar-label" data-stage-edit="bars[${i}].label">${escape(b.label || '')}</div>
|
|
66
|
+
<div class="bar-track" data-stage-key="BarChart/bar[${i}]/track">
|
|
67
|
+
<div class="bar-fill" style="${sizeProp}: 0%;"></div>
|
|
68
|
+
</div>
|
|
69
|
+
<div class="bar-value" data-stage-edit="bars[${i}].value">${escape(formatValue(b.value))}</div>
|
|
70
|
+
</div>
|
|
71
|
+
`;
|
|
72
|
+
}).join('');
|
|
73
|
+
|
|
74
|
+
el.innerHTML = `
|
|
75
|
+
<div class="barchart ${orientation}" data-stage-key="BarChart">
|
|
76
|
+
${rows}
|
|
77
|
+
</div>
|
|
78
|
+
`;
|
|
79
|
+
|
|
80
|
+
if (reveal === 'instant') {
|
|
81
|
+
// Place rows + fills in their final state with no transition.
|
|
82
|
+
el.querySelectorAll('.bar-row').forEach(n => n.classList.add('in'));
|
|
83
|
+
el.querySelectorAll('.bar-row').forEach(row => {
|
|
84
|
+
const fill = row.querySelector('.bar-fill');
|
|
85
|
+
const p = row.dataset.pct;
|
|
86
|
+
if (fill) {
|
|
87
|
+
// Suppress the transition for the initial paint only.
|
|
88
|
+
fill.style.transition = 'none';
|
|
89
|
+
fill.style[sizeProp] = `${p}%`;
|
|
90
|
+
// Force reflow then restore transition.
|
|
91
|
+
// eslint-disable-next-line no-unused-expressions
|
|
92
|
+
fill.offsetWidth;
|
|
93
|
+
fill.style.transition = '';
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const sizeProp = orientation === 'vertical' ? 'height' : 'width';
|
|
101
|
+
|
|
102
|
+
function fillRow(row, immediate) {
|
|
103
|
+
const fill = row.querySelector('.bar-fill');
|
|
104
|
+
if (!fill) return;
|
|
105
|
+
const p = row.dataset.pct;
|
|
106
|
+
if (immediate) {
|
|
107
|
+
fill.style.transition = 'none';
|
|
108
|
+
fill.style[sizeProp] = `${p}%`;
|
|
109
|
+
// eslint-disable-next-line no-unused-expressions
|
|
110
|
+
fill.offsetWidth;
|
|
111
|
+
fill.style.transition = '';
|
|
112
|
+
} else {
|
|
113
|
+
fill.style[sizeProp] = `${p}%`;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (reveal === 'animated') {
|
|
118
|
+
slide.init = function (el) {
|
|
119
|
+
const rows = Array.from(el.querySelectorAll('.bar-row'));
|
|
120
|
+
const timers = [];
|
|
121
|
+
rows.forEach((row, i) => {
|
|
122
|
+
timers.push(setTimeout(() => {
|
|
123
|
+
row.classList.add('in');
|
|
124
|
+
fillRow(row, false);
|
|
125
|
+
}, 200 + i * 220));
|
|
126
|
+
});
|
|
127
|
+
return () => timers.forEach(clearTimeout);
|
|
128
|
+
};
|
|
129
|
+
slide.replay = function (el) {
|
|
130
|
+
el.querySelectorAll('.bar-row').forEach(row => {
|
|
131
|
+
row.classList.remove('in');
|
|
132
|
+
const fill = row.querySelector('.bar-fill');
|
|
133
|
+
if (fill) {
|
|
134
|
+
fill.style.transition = 'none';
|
|
135
|
+
fill.style[sizeProp] = '0%';
|
|
136
|
+
// eslint-disable-next-line no-unused-expressions
|
|
137
|
+
fill.offsetWidth;
|
|
138
|
+
fill.style.transition = '';
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
return this.init(el);
|
|
142
|
+
};
|
|
143
|
+
} else if (reveal === 'per-click') {
|
|
144
|
+
slide.steps = bars.length;
|
|
145
|
+
slide.onStep = function (el, step) {
|
|
146
|
+
el.querySelectorAll('.bar-row').forEach((row) => {
|
|
147
|
+
const s = Number(row.dataset.step);
|
|
148
|
+
const on = s <= step;
|
|
149
|
+
row.classList.toggle('in', on);
|
|
150
|
+
const fill = row.querySelector('.bar-fill');
|
|
151
|
+
if (fill) fill.style[sizeProp] = on ? `${row.dataset.pct}%` : '0%';
|
|
152
|
+
});
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return slide;
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
function escape(s) {
|
|
160
|
+
return String(s).replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
|
161
|
+
}
|
|
162
|
+
})(typeof window !== 'undefined' ? window : globalThis);
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Stage.BeforeAfter — split comparison: image-vs-image or text-vs-text.
|
|
5
|
+
*
|
|
6
|
+
* Usage (text vs text):
|
|
7
|
+
* Stage.register(Stage.BeforeAfter({
|
|
8
|
+
* section: 69,
|
|
9
|
+
* title: '69 · Before / After',
|
|
10
|
+
* before: { label: 'Before', text: 'Manual reviews. 3-day cycles.' },
|
|
11
|
+
* after: { label: 'After', text: 'AI-drafted reviews. 30-minute cycles.' },
|
|
12
|
+
* reveal: 'staggered' // 'instant' | 'staggered' | 'slider'
|
|
13
|
+
* }));
|
|
14
|
+
*
|
|
15
|
+
* Usage (image vs image with slider):
|
|
16
|
+
* Stage.register(Stage.BeforeAfter({
|
|
17
|
+
* section: 69,
|
|
18
|
+
* before: { label: 'Before', image: 'https://picsum.photos/seed/before/1200/800' },
|
|
19
|
+
* after: { label: 'After', image: 'https://picsum.photos/seed/after/1200/800' },
|
|
20
|
+
* reveal: 'slider'
|
|
21
|
+
* }));
|
|
22
|
+
*
|
|
23
|
+
* `reveal: 'slider'` animates a diagonal divider from 0 to 50% on init (image mode).
|
|
24
|
+
*
|
|
25
|
+
* Edit paths: before.label / before.text / after.label / after.text
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
(function (root) {
|
|
29
|
+
const Stage = root.Stage = root.Stage || {};
|
|
30
|
+
|
|
31
|
+
Stage.BeforeAfter = function (opts) {
|
|
32
|
+
const before = opts.before || {};
|
|
33
|
+
const after = opts.after || {};
|
|
34
|
+
const reveal = opts.reveal || 'instant';
|
|
35
|
+
const isImage = !!(before.image || after.image);
|
|
36
|
+
const mode = isImage ? 'image' : 'text';
|
|
37
|
+
|
|
38
|
+
const slide = {
|
|
39
|
+
section: opts.section,
|
|
40
|
+
title: opts.title,
|
|
41
|
+
transition: opts.transition,
|
|
42
|
+
render(el) {
|
|
43
|
+
if (mode === 'image') {
|
|
44
|
+
el.innerHTML = `
|
|
45
|
+
<div class="before-after before-after--image" data-stage-key="BeforeAfter" style="--ba-clip: 0%;">
|
|
46
|
+
<div class="ba-pane ba-before" data-stage-key="BeforeAfter/before">
|
|
47
|
+
${before.image ? `<img class="ba-img" src="${escape(before.image)}" alt="${escape(before.label || 'before')}">` : ''}
|
|
48
|
+
<div class="ba-label ba-label--left">
|
|
49
|
+
<span class="ba-tag">${escape(before.label || 'Before')}</span>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
<div class="ba-pane ba-after" data-stage-key="BeforeAfter/after">
|
|
53
|
+
${after.image ? `<img class="ba-img" src="${escape(after.image)}" alt="${escape(after.label || 'after')}">` : ''}
|
|
54
|
+
<div class="ba-label ba-label--right">
|
|
55
|
+
<span class="ba-tag ba-tag--accent">${escape(after.label || 'After')}</span>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
<div class="ba-divider"></div>
|
|
59
|
+
</div>
|
|
60
|
+
`;
|
|
61
|
+
} else {
|
|
62
|
+
el.innerHTML = `
|
|
63
|
+
<div class="before-after before-after--text" data-stage-key="BeforeAfter">
|
|
64
|
+
<div class="ba-col ba-before" data-stage-key="BeforeAfter/before">
|
|
65
|
+
<div class="ba-col-tag">${escape(before.label || 'Before')}</div>
|
|
66
|
+
<div class="ba-col-text" data-stage-edit="before.text">${escape(before.text || '')}</div>
|
|
67
|
+
</div>
|
|
68
|
+
<div class="ba-arrow material-symbols-outlined">arrow_forward</div>
|
|
69
|
+
<div class="ba-col ba-after" data-stage-key="BeforeAfter/after">
|
|
70
|
+
<div class="ba-col-tag ba-col-tag--accent">${escape(after.label || 'After')}</div>
|
|
71
|
+
<div class="ba-col-text" data-stage-edit="after.text">${escape(after.text || '')}</div>
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (reveal === 'instant') {
|
|
78
|
+
el.querySelectorAll('.ba-pane, .ba-col, .ba-arrow').forEach(n => n.classList.add('in'));
|
|
79
|
+
if (mode === 'image') {
|
|
80
|
+
const root = el.querySelector('.before-after--image');
|
|
81
|
+
if (root) root.style.setProperty('--ba-clip', '50%');
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
if (reveal === 'staggered') {
|
|
88
|
+
slide.init = function (el) {
|
|
89
|
+
const nodes = mode === 'image'
|
|
90
|
+
? el.querySelectorAll('.ba-pane')
|
|
91
|
+
: el.querySelectorAll('.ba-col, .ba-arrow');
|
|
92
|
+
const cleanup = Stage.staggerIn(nodes, 220, 200);
|
|
93
|
+
let extra;
|
|
94
|
+
if (mode === 'image') {
|
|
95
|
+
extra = setTimeout(() => {
|
|
96
|
+
const root = el.querySelector('.before-after--image');
|
|
97
|
+
if (root) root.style.setProperty('--ba-clip', '50%');
|
|
98
|
+
}, 600);
|
|
99
|
+
}
|
|
100
|
+
return () => { cleanup(); if (extra) clearTimeout(extra); };
|
|
101
|
+
};
|
|
102
|
+
slide.replay = function (el) {
|
|
103
|
+
el.querySelectorAll('.ba-pane, .ba-col, .ba-arrow').forEach(n => n.classList.remove('in'));
|
|
104
|
+
if (mode === 'image') {
|
|
105
|
+
const root = el.querySelector('.before-after--image');
|
|
106
|
+
if (root) root.style.setProperty('--ba-clip', '0%');
|
|
107
|
+
}
|
|
108
|
+
return this.init(el);
|
|
109
|
+
};
|
|
110
|
+
} else if (reveal === 'slider') {
|
|
111
|
+
slide.init = function (el) {
|
|
112
|
+
el.querySelectorAll('.ba-pane, .ba-col, .ba-arrow').forEach(n => n.classList.add('in'));
|
|
113
|
+
const root = el.querySelector('.before-after--image');
|
|
114
|
+
if (!root) return;
|
|
115
|
+
root.style.setProperty('--ba-clip', '0%');
|
|
116
|
+
// Force reflow then animate.
|
|
117
|
+
// eslint-disable-next-line no-unused-expressions
|
|
118
|
+
root.offsetWidth;
|
|
119
|
+
const t = setTimeout(() => root.style.setProperty('--ba-clip', '50%'), 200);
|
|
120
|
+
return () => clearTimeout(t);
|
|
121
|
+
};
|
|
122
|
+
slide.replay = function (el) {
|
|
123
|
+
const root = el.querySelector('.before-after--image');
|
|
124
|
+
if (root) root.style.setProperty('--ba-clip', '0%');
|
|
125
|
+
return this.init(el);
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return slide;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
function escape(s) {
|
|
133
|
+
return String(s).replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
|
134
|
+
}
|
|
135
|
+
})(typeof window !== 'undefined' ? window : globalThis);
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Stage.Bento — modular bento grid of cells.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* Stage.register(Stage.Bento({
|
|
8
|
+
* section: 7,
|
|
9
|
+
* title: '07 · The kit',
|
|
10
|
+
* cells: [
|
|
11
|
+
* { span: 2, heading: 'Agents', body: 'long-running, async, tool-using.', color: 'accent' },
|
|
12
|
+
* { heading: 'Reviews', body: 'human in the loop.' },
|
|
13
|
+
* { heading: 'Eval', body: 'measure, then ship.', color: 'amber' },
|
|
14
|
+
* { heading: 'Specs', image: { src: 'https://...' } },
|
|
15
|
+
* { heading: 'Notes', body: 'cheap, persistent.', color: 'blue' }
|
|
16
|
+
* ]
|
|
17
|
+
* }));
|
|
18
|
+
*
|
|
19
|
+
* Layout: 4-column CSS grid. Cells default to 1 col, `span: 2` makes a cell wider.
|
|
20
|
+
*
|
|
21
|
+
* Edit paths:
|
|
22
|
+
* cells[i].heading / cells[i].body / cells[i].image.src
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
(function (root) {
|
|
26
|
+
const Stage = root.Stage = root.Stage || {};
|
|
27
|
+
|
|
28
|
+
Stage.Bento = function (opts) {
|
|
29
|
+
const cells = opts.cells || [];
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
section: opts.section,
|
|
33
|
+
title: opts.title,
|
|
34
|
+
transition: opts.transition,
|
|
35
|
+
render(el) {
|
|
36
|
+
el.innerHTML = `
|
|
37
|
+
<div class="bento" data-stage-key="Bento">
|
|
38
|
+
${cells.map((c, i) => {
|
|
39
|
+
const colorClass = c.color ? `bento-cell--${c.color}` : '';
|
|
40
|
+
const spanClass = c.span === 2 ? 'bento-cell--span-2' : '';
|
|
41
|
+
const hasImage = c.image && c.image.src;
|
|
42
|
+
const bg = hasImage
|
|
43
|
+
? `style="background-image: linear-gradient(180deg, rgba(10,10,10,0.45), rgba(10,10,10,0.85)), url('${escape(c.image.src)}')"`
|
|
44
|
+
: '';
|
|
45
|
+
return `
|
|
46
|
+
<div class="bento-cell ${spanClass} ${colorClass} ${hasImage ? 'bento-cell--image' : ''}"
|
|
47
|
+
${bg}
|
|
48
|
+
data-stage-key="Bento/cell[${i}]">
|
|
49
|
+
<div class="bento-cell-body">
|
|
50
|
+
<div class="bento-heading" data-stage-edit="cells[${i}].heading">${escape(c.heading || '')}</div>
|
|
51
|
+
${c.body ? `<div class="bento-body" data-stage-edit="cells[${i}].body">${escape(c.body)}</div>` : ''}
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
`;
|
|
55
|
+
}).join('')}
|
|
56
|
+
</div>
|
|
57
|
+
`;
|
|
58
|
+
},
|
|
59
|
+
init(el) {
|
|
60
|
+
const nodes = el.querySelectorAll('.bento-cell');
|
|
61
|
+
return Stage.staggerIn(nodes, 120, 150);
|
|
62
|
+
},
|
|
63
|
+
replay(el) {
|
|
64
|
+
el.querySelectorAll('.bento-cell').forEach(n => n.classList.remove('in'));
|
|
65
|
+
return this.init(el);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
function escape(s) {
|
|
71
|
+
return String(s).replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
|
72
|
+
}
|
|
73
|
+
})(typeof window !== 'undefined' ? window : globalThis);
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Stage.BigNumber — one massive headline number with a label.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* Stage.register(Stage.BigNumber({
|
|
8
|
+
* section: 6,
|
|
9
|
+
* title: '06 · Cost collapse',
|
|
10
|
+
* number: 92,
|
|
11
|
+
* unit: '%', // optional
|
|
12
|
+
* label: 'drop in cost-per-token',
|
|
13
|
+
* caption: '2023 → 2026, frontier models' // optional
|
|
14
|
+
* }));
|
|
15
|
+
*
|
|
16
|
+
* On init the number counts up from 0 → target in ~1.2s.
|
|
17
|
+
*
|
|
18
|
+
* Edit paths: number / unit / label / caption
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
(function (root) {
|
|
22
|
+
const Stage = root.Stage = root.Stage || {};
|
|
23
|
+
|
|
24
|
+
Stage.BigNumber = function (opts) {
|
|
25
|
+
const target = Number(opts.number) || 0;
|
|
26
|
+
const unit = opts.unit || '';
|
|
27
|
+
const label = opts.label || '';
|
|
28
|
+
const caption = opts.caption || '';
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
section: opts.section,
|
|
32
|
+
title: opts.title,
|
|
33
|
+
transition: opts.transition,
|
|
34
|
+
render(el) {
|
|
35
|
+
el.innerHTML = `
|
|
36
|
+
<div class="big-number" data-stage-key="BigNumber">
|
|
37
|
+
<div class="bn-figure" data-stage-key="BigNumber/figure">
|
|
38
|
+
<span class="bn-num" data-stage-edit="number" data-target="${target}">0</span>${unit ? `<span class="bn-unit" data-stage-edit="unit">${escape(unit)}</span>` : ''}
|
|
39
|
+
</div>
|
|
40
|
+
<div class="bn-label" data-stage-edit="label" data-stage-key="BigNumber/label">${escape(label)}</div>
|
|
41
|
+
${caption ? `<div class="bn-caption" data-stage-edit="caption" data-stage-key="BigNumber/caption">${escape(caption)}</div>` : ''}
|
|
42
|
+
</div>
|
|
43
|
+
`;
|
|
44
|
+
},
|
|
45
|
+
init(el) {
|
|
46
|
+
const numEl = el.querySelector('.bn-num');
|
|
47
|
+
const unitEl = el.querySelector('.bn-unit');
|
|
48
|
+
const labelEl = el.querySelector('.bn-label');
|
|
49
|
+
const capEl = el.querySelector('.bn-caption');
|
|
50
|
+
const duration = 1200;
|
|
51
|
+
const start = performance.now();
|
|
52
|
+
const isInt = Number.isInteger(target);
|
|
53
|
+
let rafId;
|
|
54
|
+
function tick(now) {
|
|
55
|
+
const t = Math.min(1, (now - start) / duration);
|
|
56
|
+
// easeOutCubic
|
|
57
|
+
const eased = 1 - Math.pow(1 - t, 3);
|
|
58
|
+
const value = target * eased;
|
|
59
|
+
numEl.textContent = isInt ? Math.floor(value).toLocaleString() : value.toFixed(1);
|
|
60
|
+
if (t < 1) rafId = requestAnimationFrame(tick);
|
|
61
|
+
else numEl.textContent = isInt ? target.toLocaleString() : String(target);
|
|
62
|
+
}
|
|
63
|
+
rafId = requestAnimationFrame(tick);
|
|
64
|
+
|
|
65
|
+
const timers = [];
|
|
66
|
+
timers.push(setTimeout(() => unitEl && unitEl.classList.add('in'), 200));
|
|
67
|
+
timers.push(setTimeout(() => labelEl && labelEl.classList.add('in'), 600));
|
|
68
|
+
timers.push(setTimeout(() => capEl && capEl.classList.add('in'), 1100));
|
|
69
|
+
|
|
70
|
+
return () => {
|
|
71
|
+
cancelAnimationFrame(rafId);
|
|
72
|
+
timers.forEach(clearTimeout);
|
|
73
|
+
};
|
|
74
|
+
},
|
|
75
|
+
replay(el) {
|
|
76
|
+
const numEl = el.querySelector('.bn-num');
|
|
77
|
+
if (numEl) numEl.textContent = '0';
|
|
78
|
+
el.querySelectorAll('.bn-unit, .bn-label, .bn-caption').forEach(n => n.classList.remove('in'));
|
|
79
|
+
return this.init(el);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
function escape(s) {
|
|
85
|
+
return String(s).replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
|
86
|
+
}
|
|
87
|
+
})(typeof window !== 'undefined' ? window : globalThis);
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Stage.Callout — sidebar callout box with colored left border and icon.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* Stage.register(Stage.Callout({
|
|
8
|
+
* section: 67,
|
|
9
|
+
* title: '67 · Callout',
|
|
10
|
+
* kind: 'tip', // 'info' | 'tip' | 'warning' | 'danger' | 'success'
|
|
11
|
+
* icon: 'lightbulb', // optional override; default per kind
|
|
12
|
+
* heading: 'Pro tip',
|
|
13
|
+
* body: 'Keep your specs short and your reviews shorter.',
|
|
14
|
+
* reveal: 'staggered' // 'instant' | 'staggered'
|
|
15
|
+
* }));
|
|
16
|
+
*
|
|
17
|
+
* Edit paths: heading / body
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
(function (root) {
|
|
21
|
+
const Stage = root.Stage = root.Stage || {};
|
|
22
|
+
|
|
23
|
+
const DEFAULTS = {
|
|
24
|
+
info: { icon: 'info' },
|
|
25
|
+
tip: { icon: 'lightbulb' },
|
|
26
|
+
warning: { icon: 'warning' },
|
|
27
|
+
danger: { icon: 'error' },
|
|
28
|
+
success: { icon: 'check_circle' }
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
Stage.Callout = function (opts) {
|
|
32
|
+
const kind = DEFAULTS[opts.kind] ? opts.kind : 'info';
|
|
33
|
+
const icon = opts.icon || DEFAULTS[kind].icon;
|
|
34
|
+
const reveal = opts.reveal || 'instant';
|
|
35
|
+
|
|
36
|
+
const slide = {
|
|
37
|
+
section: opts.section,
|
|
38
|
+
title: opts.title,
|
|
39
|
+
transition: opts.transition,
|
|
40
|
+
render(el) {
|
|
41
|
+
el.innerHTML = `
|
|
42
|
+
<div class="callout callout--${kind}" data-stage-key="Callout">
|
|
43
|
+
<span class="callout-icon material-symbols-outlined">${escape(icon)}</span>
|
|
44
|
+
<div class="callout-body">
|
|
45
|
+
<div class="callout-heading" data-stage-edit="heading" data-stage-key="Callout/heading">${escape(opts.heading || '')}</div>
|
|
46
|
+
<div class="callout-text" data-stage-edit="body" data-stage-key="Callout/body">${escape(opts.body || '')}</div>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
`;
|
|
50
|
+
if (reveal === 'instant') {
|
|
51
|
+
el.querySelector('.callout')?.classList.add('in');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
if (reveal === 'staggered') {
|
|
57
|
+
slide.init = function (el) {
|
|
58
|
+
const co = el.querySelector('.callout');
|
|
59
|
+
const t1 = setTimeout(() => co?.classList.add('in'), 120);
|
|
60
|
+
const t2 = setTimeout(() => co?.classList.add('reveal-text'), 480);
|
|
61
|
+
return () => { clearTimeout(t1); clearTimeout(t2); };
|
|
62
|
+
};
|
|
63
|
+
slide.replay = function (el) {
|
|
64
|
+
el.querySelector('.callout')?.classList.remove('in', 'reveal-text');
|
|
65
|
+
return this.init(el);
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return slide;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
function escape(s) {
|
|
73
|
+
return String(s).replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
|
74
|
+
}
|
|
75
|
+
})(typeof window !== 'undefined' ? window : globalThis);
|