@kaydash9999/banker-plugins 0.22.0 → 0.23.2
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +68 -0
- package/PRIVACY.md +1 -0
- package/README.md +39 -13
- package/bin/banker.js +118 -22
- package/bin/lib/codex-hooks.mjs +116 -18
- package/bin/lib/codex-names.mjs +4 -0
- package/bin/lib/codex-routes.mjs +93 -1
- package/bin/lib/command-skill.cjs +37 -6
- package/codex/command-skills/front-qa/SKILL.md +1 -1
- package/codex/command-skills/setup/SKILL.md +4 -4
- package/codex/hooks.json +5 -5
- package/codex/manifest.json +18 -15
- package/codex/skills/progress/scripts/progress.mjs +44 -14
- package/codex/skills/teamwork/SKILL.md +143 -0
- package/codex/skills/teamwork/scripts/teamwork.mjs +1007 -0
- package/codex/transform-matrix.md +5 -3
- package/hooks/obsidize-hook.mjs +75 -32
- package/hooks/telemetry-count-codex.mjs +238 -57
- package/hooks/update-fetch.mjs +2 -1
- package/hooks/update-notify.mjs +82 -20
- package/package.json +3 -1
- package/skills/3d-intro-build/SKILL.md +101 -20
- package/skills/3d-intro-build/references/NOTICE.md +9 -2
- package/skills/3d-intro-build/references/assemble.mjs +113 -22
- package/skills/3d-intro-build/references/azure-adapter.mjs +110 -24
- package/skills/3d-intro-build/references/clip-post.mjs +443 -0
- package/skills/3d-intro-build/references/dive-mode.mjs +299 -0
- package/skills/3d-intro-build/references/intro-fixes.css +11 -0
- package/skills/3d-intro-build/references/prompts.md +51 -4
- package/skills/3d-intro-build/references/scrub-engine.js +1 -0
- package/skills/3d-intro-build/references/step-nav.css +29 -0
- package/skills/3d-intro-build/references/step-nav.js +284 -106
- package/skills/3d-intro-build/references/verify-intro.mjs +373 -32
- package/skills/3d-intro-build/references/verify.md +6 -0
- package/skills/3d-intro-build/references/video-pool.mjs +142 -12
- package/skills/3d-intro-setup/SKILL.md +20 -7
- package/skills/3d-intro-setup/references/azure-adapter.mjs +110 -24
- package/skills/3d-intro-setup/references/video-pool.mjs +142 -12
- package/skills/all-in-one/SKILL.md +125 -86
- package/skills/omc-patch/SKILL.md +1 -1
- package/skills/payload-mon/SKILL.md +1 -1
- package/skills/play-qa/SKILL.md +6 -4
- package/skills/progress-mon/README.md +164 -0
- package/skills/progress-mon/SKILL.md +165 -0
- package/skills/progress-mon/assets/fixture/README.md +1 -0
- package/skills/progress-mon/assets/fixture/arch.json +33 -0
- package/skills/progress-mon/assets/fixture/commits.json +7 -0
- package/skills/progress-mon/assets/fixture/config.json +30 -0
- package/skills/progress-mon/assets/fixture/events.jsonl +7 -0
- package/skills/progress-mon/assets/fixture/flow.json +44 -0
- package/skills/progress-mon/assets/fixture/items.json +14 -0
- package/skills/progress-mon/assets/fixture/req-spec.json +31 -0
- package/skills/progress-mon/assets/fixture/reqs.json +11 -0
- package/skills/progress-mon/assets/fixture/variants/arch-auto/arch.json +58 -0
- package/skills/progress-mon/assets/fixture/variants/check-auto/arch.json +15 -0
- package/skills/progress-mon/assets/fixture/variants/check-overlap/arch.json +14 -0
- package/skills/progress-mon/assets/fixture/variants/history-manual/config.json +13 -0
- package/skills/progress-mon/assets/fixture/variants/history-manual/items.json +7 -0
- package/skills/progress-mon/assets/fixture/variants/workflow-wide/flow.json +35 -0
- package/skills/progress-mon/assets/fixture/workflows/wf_ex1/agent-a01.jsonl +1 -0
- package/skills/progress-mon/assets/fixture/workflows/wf_ex1/agent-a02.jsonl +1 -0
- package/skills/progress-mon/assets/fixture/workflows/wf_ex1/agent-a03.jsonl +1 -0
- package/skills/progress-mon/assets/fixture/workflows/wf_ex1/agent-a04.jsonl +1 -0
- package/skills/progress-mon/assets/fixture/workflows/wf_ex1/journal.jsonl +7 -0
- package/skills/progress-mon/references/arch.md +280 -0
- package/skills/progress-mon/references/authoring.md +199 -0
- package/skills/progress-mon/references/config.md +115 -0
- package/skills/progress-mon/references/flow.md +75 -0
- package/skills/progress-mon/references/inventory.md +58 -0
- package/skills/progress-mon/references/items.md +74 -0
- package/skills/progress-mon/references/req-spec.md +74 -0
- package/skills/progress-mon/references/reqs.md +30 -0
- package/skills/progress-mon/scripts/index.js +31 -0
- package/skills/progress-mon/scripts/pm-check.mjs +549 -0
- package/skills/progress-mon/scripts/pm-config.mjs +343 -0
- package/skills/progress-mon/scripts/pm-data.mjs +394 -0
- package/skills/progress-mon/scripts/pm-html.mjs +77 -0
- package/skills/progress-mon/scripts/pm-memory.mjs +333 -0
- package/skills/progress-mon/scripts/pm-render.mjs +1644 -0
- package/skills/progress-mon/scripts/pm-server.mjs +781 -0
- package/skills/progress-mon/scripts/pm-sources.mjs +501 -0
- package/skills/progress-mon/scripts/progress-mon.mjs +167 -0
- package/skills/ralph-qa/SKILL.md +107 -17
- package/skills/ralph-qa/references/gate-evidence.mjs +457 -0
- package/skills/ralph-qa/references/payload-scan.mjs +19 -2
- package/skills/ralph-qa/references/run-folder.mjs +299 -0
- package/skills/ralph-qa/references/seat-payload.mjs +60 -13
- package/skills/ralph-qa/references/seat-run.mjs +200 -159
- package/skills/remains/SKILL.md +2 -0
- package/skills/remains/scripts/boxes.mjs +6 -2
- package/skills/setup-bypass-permissions/scripts/codex-config.mjs +19 -8
- package/skills/setup-mcp/SKILL.md +1 -1
- package/skills/setup-omc-hud/SKILL.md +2 -1
- package/skills/setup-omc-hud/assets/codex-config.mjs +19 -8
- package/skills/ultra-chain/SKILL.md +32 -14
- package/skills/ultra-chain/references/chain-ledger.mjs +111 -3
- package/skills/ultra-init/SKILL.md +30 -22
- package/skills/ultraqa/NOTICE.md +34 -0
- package/skills/ultraqa/SKILL.md +197 -0
- package/skills/ultraqa/references/gate-evidence.mjs +457 -0
- package/skills/ultraqa/references/run-folder.mjs +299 -0
- package/skills/ultraqa/references/tmux-drive.md +52 -0
- package/skills/ultraqa/references/uqa-run.mjs +339 -0
- package/skills/update-banker/SKILL.md +1 -1
|
@@ -1,13 +1,23 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Scene-by-scene navigation for the scroll-world intro (
|
|
2
|
+
* Scene-by-scene navigation for the scroll-world intro (on by default; intro.json "stepNav": false
|
|
3
|
+
* turns it off).
|
|
3
4
|
*
|
|
4
5
|
* The vendored engine maps the window's scroll position to clip time and stays as shipped; this
|
|
5
|
-
* script only moves the scroll position.
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
6
|
+
* script only moves the scroll position. Stops: each scene's start (its dive's first frame; an exit
|
|
7
|
+
* clip is the way to the next scene), the end of the last scene, then the top of the page body
|
|
8
|
+
* (`end`, when the intro sits on top of a site). Between two stops lies a half point.
|
|
9
|
+
* wheel one notch moves half way to the next stop, two notches a whole scene. Pixel, line
|
|
10
|
+
* and page deltas, and a touchpad's small ones, add up to notches (notchPx, linePx);
|
|
11
|
+
* one gesture moves at most maxNotches
|
|
12
|
+
* arrow keys up/down and left/right as the wheel (up and left go back)
|
|
13
|
+
* PageUp/PageDown, Space (shift goes back), a swipe over swipePx one scene
|
|
14
|
+
* Home, End the first scene's start, the last scene's end, at once (no animation)
|
|
15
|
+
* a route dot its scene's start, at once (a click, or Enter or Space on the focused dot)
|
|
16
|
+
* Fields and editable places keep their keys. An input during a move retargets it from where that
|
|
17
|
+
* move was going. When the page comes to rest anywhere else above the body (a scrollbar, a touchpad,
|
|
18
|
+
* an anchor), it settles on the nearest stop after snapMs. Below the body top the browser scrolls as
|
|
19
|
+
* usual. At a stop or a half point, the copy of the scene in view is shown (class isn-copy, faded by
|
|
20
|
+
* step-nav.css; the engine's own fade takes over again when a move starts).
|
|
11
21
|
*
|
|
12
22
|
* Options (intro.json "stepNav", all optional; values are the defaults below):
|
|
13
23
|
* end selector of the body start; omit on a standalone intro page. It is looked up at
|
|
@@ -18,8 +28,14 @@
|
|
|
18
28
|
* swipePx a touch swipe longer than this is one step
|
|
19
29
|
* durBaseMs, durPerVhMs, durMaxMs a move takes min(durMax, durBase + distance in screen heights
|
|
20
30
|
* x durPerVh) ms with a cosine ease; 0 under prefers-reduced-motion
|
|
21
|
-
*
|
|
22
|
-
*
|
|
31
|
+
* notchPx, linePx, maxNotches wheel pixels to a notch, pixels in a line delta, notches per gesture
|
|
32
|
+
* snapMs how long the page must rest off the stops before it settles on the nearest one
|
|
33
|
+
* decodeCapMs, instantDecodeCapMs the longest wait for the posters to decode before a move, and before
|
|
34
|
+
* an instant one (nothing hides an undecoded poster there, so it waits longer)
|
|
35
|
+
* loadingDelayMs, loadingLabel a wait for the posters (either kind of move) longer than loadingDelayMs
|
|
36
|
+
* shows a small loading mark mid-screen with this text (a status region, read by a screen
|
|
37
|
+
* reader; it takes no focus and no input, and a new input drops it with the old move). It
|
|
38
|
+
* goes as the move starts. Under reduced motion the mark does not spin
|
|
23
39
|
* prefetch download the clips at load so the first move already plays video; on a phone or
|
|
24
40
|
* with Save-Data only the first three in playback order
|
|
25
41
|
* hintLabel, skipLabel, mainLabel texts of the mouse hint, the skip link and the main button
|
|
@@ -37,7 +53,8 @@
|
|
|
37
53
|
|
|
38
54
|
const DEFAULTS = Object.freeze({
|
|
39
55
|
end: null, gestureGapMs: 220, minWheel: 2, swipePx: 30,
|
|
40
|
-
durBaseMs: 2600, durPerVhMs: 760, durMaxMs: 5600,
|
|
56
|
+
durBaseMs: 2600, durPerVhMs: 760, durMaxMs: 5600, decodeCapMs: 300, prefetch: true,
|
|
57
|
+
notchPx: 100, linePx: 40, maxNotches: 2, snapMs: 400, instantDecodeCapMs: 1500, loadingDelayMs: 150, loadingLabel: 'Loading',
|
|
41
58
|
hintLabel: 'Scroll or press the down key', skipLabel: 'Skip intro', mainLabel: 'Go to main content',
|
|
42
59
|
});
|
|
43
60
|
|
|
@@ -63,16 +80,64 @@
|
|
|
63
80
|
/** The scroll length of the segments in screen heights (the engine's track adds one screen). */
|
|
64
81
|
function totalWeight(config) { return spansOf(config).total; }
|
|
65
82
|
|
|
66
|
-
/** Stop positions in px:
|
|
83
|
+
/** Stop positions in px: each scene's start, the last scene's end, then the body. */
|
|
67
84
|
function stopPositions(config, vh, endTop) {
|
|
68
85
|
const spans = spansOf(config).spans;
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
});
|
|
86
|
+
// one pixel into the scene: the engine's current segment is the last whose start is at or above the
|
|
87
|
+
// page, and its starts carry float error (2.2 x 800 = 1760.0000000000002), so a stop at the rounded
|
|
88
|
+
// start would leave the segment before on top; an exit ending on a background plate showed the plate
|
|
89
|
+
const stops = spans.map(function (span, i) { return i ? Math.floor(span[0] * vh) + 1 : 0; });
|
|
90
|
+
if (spans.length) stops.push(Math.round(spans[spans.length - 1][1] * vh));
|
|
73
91
|
return endTop == null ? stops : stops.concat([endTop]);
|
|
74
92
|
}
|
|
75
93
|
|
|
94
|
+
/** The stops with the half point between each two: where a wheel notch or an arrow key can land. */
|
|
95
|
+
function gridOf(stops) {
|
|
96
|
+
const g = [];
|
|
97
|
+
stops.forEach(function (v, i) { if (i) g.push(Math.round((stops[i - 1] + v) / 2)); g.push(v); });
|
|
98
|
+
return g;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Where n points of `list` (signed: + goes down) take the page from `base`; from a place between
|
|
103
|
+
* points the first one is the next point that way. null when there is none that way.
|
|
104
|
+
*/
|
|
105
|
+
function stepFrom(list, base, n) {
|
|
106
|
+
if (!n) return null;
|
|
107
|
+
let i;
|
|
108
|
+
if (n > 0) i = list.findIndex(function (v) { return v > base + 1; });
|
|
109
|
+
else for (i = list.length - 1; i >= 0 && !(list[i] < base - 1); i--);
|
|
110
|
+
if (i < 0) return null;
|
|
111
|
+
return list[Math.max(0, Math.min(list.length - 1, i + (n > 0 ? n - 1 : n + 1)))];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** The scene whose copy is in view at y: the dive holding it, or across an exit the nearer scene. */
|
|
115
|
+
function sceneAt(config, vh, y) {
|
|
116
|
+
const spans = spansOf(config).spans;
|
|
117
|
+
for (let i = spans.length - 1; i >= 0; i--) {
|
|
118
|
+
if (y >= spans[i][0] * vh - 1) {
|
|
119
|
+
const end = spans[i][1] * vh;
|
|
120
|
+
const next = spans[i + 1];
|
|
121
|
+
return y <= end + 1 || !next ? i : (y - end > (next[0] * vh - end) / 2 ? i + 1 : i);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return 0;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** The stop a page resting at y settles on (the nearest up to the last scene's end); -1 at a stop or a half point. */
|
|
128
|
+
function settleTarget(stops, lastStop, y) {
|
|
129
|
+
if (gridOf(stops).some(function (v) { return Math.abs(v - y) <= 1; })) return -1;
|
|
130
|
+
return nearest(stops.slice(0, lastStop + 1), y);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Wheel notches in one event: pixel, line and page deltas in pixels, added to the gesture's remainder. */
|
|
134
|
+
function wheelNotches(e, carry, o) {
|
|
135
|
+
const unit = e.deltaMode === 1 ? o.linePx : e.deltaMode === 2 ? 2 * o.notchPx : 1;
|
|
136
|
+
const total = carry + (e.deltaY || 0) * unit;
|
|
137
|
+
const n = Math.trunc(total / o.notchPx);
|
|
138
|
+
return { n: n, carry: total - n * o.notchPx };
|
|
139
|
+
}
|
|
140
|
+
|
|
76
141
|
function moveDuration(distance, vh, o, reduce) {
|
|
77
142
|
if (reduce) return 0;
|
|
78
143
|
return Math.min(o.durMaxMs, o.durBaseMs + (Math.abs(distance) / vh) * o.durPerVhMs);
|
|
@@ -133,67 +198,49 @@
|
|
|
133
198
|
return best;
|
|
134
199
|
};
|
|
135
200
|
|
|
136
|
-
//
|
|
137
|
-
// next
|
|
138
|
-
function targetOf(stops,
|
|
139
|
-
if (
|
|
140
|
-
if (input
|
|
141
|
-
|
|
142
|
-
return -1;
|
|
201
|
+
// Where an input asks to go from `base`: { to: i } a stop, { steps: n } n grid points, { scenes: n } the
|
|
202
|
+
// next stop that way (n is +1 or -1). null when there is nothing that way.
|
|
203
|
+
function targetOf(stops, base, input) {
|
|
204
|
+
if (input.to != null) return stops[Math.max(0, Math.min(stops.length - 1, input.to))];
|
|
205
|
+
if (input.steps) return stepFrom(gridOf(stops), base, input.steps);
|
|
206
|
+
return stepFrom(stops, base, input.scenes);
|
|
143
207
|
}
|
|
144
208
|
|
|
145
209
|
/**
|
|
146
|
-
* Moves between stops. input(
|
|
147
|
-
*
|
|
148
|
-
* onArrive(
|
|
210
|
+
* Moves between stops and half points. input(spec) starts a move from where the page stands (sync(y)),
|
|
211
|
+
* or during a move retargets it from where it was going. move(fromY, toY, done) animates from the
|
|
212
|
+
* page's place (where()); onArrive(y) runs when a move ends; onLeave() when one starts.
|
|
149
213
|
*/
|
|
150
214
|
function createNavigator(opt) {
|
|
151
|
-
let index = 0;
|
|
152
215
|
let pos = 0;
|
|
153
|
-
let target =
|
|
154
|
-
let queued = null;
|
|
155
|
-
let guardUntil = -Infinity;
|
|
216
|
+
let target = null;
|
|
156
217
|
let generation = 0;
|
|
218
|
+
let arrivals = 0;
|
|
157
219
|
const list = function () { return opt.stops(); };
|
|
158
|
-
function
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
target
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
if (to < 0 || Math.abs(list()[to] - pos) <= 1) return;
|
|
171
|
-
if (input === 'down' && index === opt.lastScene && opt.now() < guardUntil) return;
|
|
172
|
-
const from = index;
|
|
173
|
-
const gen = generation;
|
|
174
|
-
target = to;
|
|
175
|
-
opt.move(pos, list()[to], function () { if (gen === generation) arrive(from, to); });
|
|
220
|
+
function go(y, instant) {
|
|
221
|
+
if (y == null || Math.abs(y - (target == null ? pos : target)) <= 1) return;
|
|
222
|
+
const gen = ++generation;
|
|
223
|
+
if (target == null && opt.onLeave) opt.onLeave(instant);
|
|
224
|
+
target = y;
|
|
225
|
+
opt.move(opt.where ? opt.where() : pos, y, function () {
|
|
226
|
+
if (gen !== generation) return;
|
|
227
|
+
pos = y;
|
|
228
|
+
target = null;
|
|
229
|
+
arrivals++;
|
|
230
|
+
if (opt.onArrive) opt.onArrive(y);
|
|
231
|
+
}, Boolean(instant));
|
|
176
232
|
}
|
|
177
233
|
return {
|
|
178
|
-
input: function (
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
},
|
|
183
|
-
sync: function (y) {
|
|
184
|
-
if (target !== -1) return;
|
|
185
|
-
pos = y;
|
|
186
|
-
index = nearest(list(), y);
|
|
187
|
-
},
|
|
188
|
-
// Drops the move in flight and the queued input; the move's end, when it comes, starts nothing.
|
|
189
|
-
cancel: function () {
|
|
190
|
-
generation++;
|
|
191
|
-
target = -1;
|
|
192
|
-
queued = null;
|
|
193
|
-
},
|
|
234
|
+
input: function (spec) { go(targetOf(list(), target == null ? pos : target, spec), spec.instant); },
|
|
235
|
+
sync: function (y) { if (target == null) pos = y; },
|
|
236
|
+
// Drops the move in flight; its end, when it comes, does nothing.
|
|
237
|
+
cancel: function () { generation++; target = null; },
|
|
194
238
|
state: function () {
|
|
195
|
-
const
|
|
196
|
-
|
|
239
|
+
const stops = list();
|
|
240
|
+
const at = target == null ? pos : target;
|
|
241
|
+
const index = nearest(stops, at);
|
|
242
|
+
return { index: index, y: at, moving: target != null, arrivals: arrivals, onStop: Math.abs(stops[index] - at) <= 1,
|
|
243
|
+
atBody: stops.length - 1 > opt.lastStop && index === stops.length - 1 && Math.abs(stops[index] - at) <= 1 };
|
|
197
244
|
},
|
|
198
245
|
};
|
|
199
246
|
}
|
|
@@ -207,15 +254,26 @@
|
|
|
207
254
|
const pressable = function (t) {
|
|
208
255
|
return Boolean(t) && (/^(BUTTON|A|SUMMARY)$/.test(t.tagName || '') || /^(button|link|checkbox|switch|menuitem|tab|option)$/.test(role(t)));
|
|
209
256
|
};
|
|
210
|
-
|
|
257
|
+
// The intro's own controls (the route dots and the nav items) keep moving the scene on Left/Right after a click
|
|
258
|
+
// focuses them; another focused button or link (in scene content) leaves Left/Right to itself.
|
|
259
|
+
const ownControl = function (t) { return Boolean(t && t.closest && t.closest('.sw-nav, .sw-route')); };
|
|
260
|
+
const KEYS = { ArrowDown: { steps: 1 }, ArrowRight: { steps: 1 }, ArrowUp: { steps: -1 }, ArrowLeft: { steps: -1 },
|
|
261
|
+
PageDown: { scenes: 1 }, PageUp: { scenes: -1 }, Home: { to: 0, instant: true }, End: { to: 'last', instant: true } };
|
|
211
262
|
|
|
212
|
-
/**
|
|
263
|
+
/** What a key asks for ({ steps }, { scenes }, { to }; End's to is 'last'), or null when the key is not the intro's. */
|
|
213
264
|
function keyInput(e) {
|
|
214
265
|
if (e.altKey || e.ctrlKey || e.metaKey || editable(e.target)) return null;
|
|
215
|
-
if (e.key === ' ') return pressable(e.target) ? null :
|
|
266
|
+
if (e.key === ' ') return pressable(e.target) ? null : { scenes: e.shiftKey ? -1 : 1 };
|
|
267
|
+
if ((e.key === 'ArrowLeft' || e.key === 'ArrowRight') && pressable(e.target) && !ownControl(e.target)) return null;
|
|
216
268
|
return KEYS[e.key] || null;
|
|
217
269
|
}
|
|
218
270
|
|
|
271
|
+
/** The direction of an input from `base`: 'down', 'up', or null when it asks for no move. */
|
|
272
|
+
function dirOf(stops, base, input) {
|
|
273
|
+
const y = targetOf(stops, base, input);
|
|
274
|
+
return y == null || Math.abs(y - base) <= 1 ? null : (y > base ? 'down' : 'up');
|
|
275
|
+
}
|
|
276
|
+
|
|
219
277
|
// ---- browser wiring -----------------------------------------------------------
|
|
220
278
|
|
|
221
279
|
const ease = function (p) { return 0.5 - 0.5 * Math.cos(Math.PI * p); };
|
|
@@ -226,9 +284,46 @@
|
|
|
226
284
|
try { window.scrollTo({ top: y, behavior: 'instant' }); } catch (e) { window.scrollTo(0, y); }
|
|
227
285
|
}
|
|
228
286
|
|
|
287
|
+
// A scene a move shows paints nothing until its poster is decoded (the engine's posters decode async):
|
|
288
|
+
// a jump showed an empty background for 0.1-0.35 s. Decode every poster first, waiting at most capMs.
|
|
289
|
+
function postersReady(world, capMs, then) {
|
|
290
|
+
let fired = false;
|
|
291
|
+
const go = function () { if (!fired) { fired = true; then(); } };
|
|
292
|
+
const stills = Array.prototype.slice.call(world.querySelectorAll('.sw-scene__still'));
|
|
293
|
+
Promise.all(stills.map(function (i) { return i.decode ? i.decode().catch(function () {}) : null; })).then(go);
|
|
294
|
+
setTimeout(go, capMs);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* The loading mark of the poster waits: begin(token) shows it after delayMs unless end(token) came first;
|
|
299
|
+
* a newer begin() or cancel() drops the older wait and its mark. show() and hide() draw it; timers injectable.
|
|
300
|
+
*/
|
|
301
|
+
function createLoader(delayMs, show, hide, timers) {
|
|
302
|
+
// wrapped: a browser refuses setTimeout called as a method of another object (Illegal invocation)
|
|
303
|
+
const t = timers || { set: function (f, ms) { return setTimeout(f, ms); }, clear: function (id) { clearTimeout(id); } };
|
|
304
|
+
let current = null;
|
|
305
|
+
let timer = null;
|
|
306
|
+
let shown = false;
|
|
307
|
+
function drop() {
|
|
308
|
+
if (timer !== null) t.clear(timer);
|
|
309
|
+
timer = null;
|
|
310
|
+
if (shown) { shown = false; hide(); }
|
|
311
|
+
}
|
|
312
|
+
return {
|
|
313
|
+
begin: function (token) {
|
|
314
|
+
drop();
|
|
315
|
+
current = token;
|
|
316
|
+
timer = t.set(function () { timer = null; if (current === token) { shown = true; show(); } }, delayMs);
|
|
317
|
+
},
|
|
318
|
+
end: function (token) { if (token === current) { drop(); current = null; } },
|
|
319
|
+
cancel: function () { drop(); current = null; },
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
229
323
|
// The running animation; a newer one, or stopAnimation(), ends it between frames.
|
|
230
324
|
let animation = 0;
|
|
231
|
-
|
|
325
|
+
let pending = 0; // a move waiting for its posters; a newer move or stopAnimation() drops it
|
|
326
|
+
function stopAnimation() { animation++; pending++; }
|
|
232
327
|
|
|
233
328
|
function animate(from, to, duration, done) {
|
|
234
329
|
const mine = ++animation;
|
|
@@ -321,23 +416,39 @@
|
|
|
321
416
|
document.body.insertBefore(skip, document.body.firstChild);
|
|
322
417
|
document.body.appendChild(hint);
|
|
323
418
|
if (o.end) document.body.appendChild(main);
|
|
324
|
-
|
|
419
|
+
// a status region: its text is set while a poster wait shows, so a screen reader hears it; no focus taken
|
|
420
|
+
const loading = element('div', 'isn-loading');
|
|
421
|
+
loading.setAttribute('role', 'status');
|
|
422
|
+
loading.setAttribute('aria-live', 'polite');
|
|
423
|
+
const spin = element('i', 'isn-loading__spin');
|
|
424
|
+
spin.setAttribute('aria-hidden', 'true');
|
|
425
|
+
loading.appendChild(spin);
|
|
426
|
+
loading.appendChild(element('span', 'isn-loading__label'));
|
|
427
|
+
document.body.appendChild(loading);
|
|
428
|
+
return { hint: hint, skip: skip, main: main, loading: loading };
|
|
325
429
|
}
|
|
326
430
|
|
|
327
|
-
/**
|
|
431
|
+
/**
|
|
432
|
+
* Wires the navigation to the page. Returns { goTo(i), hold(on), state(), stops(), lastScene, lastStop, end };
|
|
433
|
+
* hold(true) stops the settling on a stop (a measuring script that parks the page between stops).
|
|
434
|
+
*/
|
|
328
435
|
function attach(world, config, options) {
|
|
329
436
|
const o = Object.assign({}, DEFAULTS, options || {});
|
|
330
437
|
const ctx = makeContext(world, config, o);
|
|
331
438
|
ctx.ui = buildUi(o);
|
|
439
|
+
const label = ctx.ui.loading.querySelector('.isn-loading__label');
|
|
440
|
+
ctx.loader = createLoader(o.loadingDelayMs,
|
|
441
|
+
function () { label.textContent = o.loadingLabel; ctx.ui.loading.classList.add('is-on'); },
|
|
442
|
+
function () { ctx.ui.loading.classList.remove('is-on'); label.textContent = ''; });
|
|
332
443
|
listenWheel(ctx);
|
|
333
444
|
listenKeys(ctx);
|
|
334
445
|
listenTouch(ctx);
|
|
335
446
|
listenClicks(ctx);
|
|
336
447
|
listenScroll(ctx);
|
|
337
448
|
ctx.nav.sync(window.scrollY);
|
|
338
|
-
ctx.mark(
|
|
339
|
-
const handle = { goTo: function (i) { ctx
|
|
340
|
-
state: function () { return ctx.nav.state(); }, stops: ctx.stops, lastScene: ctx.lastScene, end: o.end };
|
|
449
|
+
ctx.mark();
|
|
450
|
+
const handle = { goTo: function (i) { step(ctx, { to: i }); }, hold: function (on) { ctx.held = Boolean(on); },
|
|
451
|
+
state: function () { return ctx.nav.state(); }, stops: ctx.stops, lastScene: ctx.lastScene, lastStop: ctx.lastStop, end: o.end };
|
|
341
452
|
// The navigation on this page, for scripts and for verify-intro.mjs.
|
|
342
453
|
window.IntroStepNav.current = handle;
|
|
343
454
|
return handle;
|
|
@@ -346,7 +457,7 @@
|
|
|
346
457
|
function makeContext(world, config, o) {
|
|
347
458
|
const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
348
459
|
const lastScene = (config.sections || []).length - 1;
|
|
349
|
-
const ctx = { o: o, world: world, lastScene: lastScene, focusEnd: false, warned: false };
|
|
460
|
+
const ctx = { o: o, world: world, config: config, lastScene: lastScene, lastStop: lastScene + 1, focusEnd: false, warned: false };
|
|
350
461
|
ctx.endEl = function () { return endElement(ctx); };
|
|
351
462
|
ctx.endTop = function () { const e = ctx.endEl(); return e ? Math.round(e.getBoundingClientRect().top + window.scrollY) : null; };
|
|
352
463
|
// The engine sizes its track (segments + one screen) from the height it last laid out at, so the
|
|
@@ -357,11 +468,24 @@
|
|
|
357
468
|
return h > 0 ? h / (totalWeight(config) + 1) : window.innerHeight;
|
|
358
469
|
};
|
|
359
470
|
ctx.stops = function () { return stopPositions(config, ctx.vh(), ctx.endTop()); };
|
|
360
|
-
ctx.ours = function (y, dir) { return belongsToIntro(y, dir, ctx.stops()[
|
|
361
|
-
ctx.mark = function (
|
|
362
|
-
ctx.nav = createNavigator({ stops: ctx.stops,
|
|
363
|
-
|
|
364
|
-
|
|
471
|
+
ctx.ours = function (y, dir) { return belongsToIntro(y, dir, ctx.stops()[ctx.lastStop], ctx.endTop()); };
|
|
472
|
+
ctx.mark = function () { mark(ctx); };
|
|
473
|
+
ctx.nav = createNavigator({ stops: ctx.stops, lastStop: ctx.lastStop, where: function () { return window.scrollY; },
|
|
474
|
+
onLeave: function (instant) { instantCopy(instant); showCopy(ctx, -1); },
|
|
475
|
+
onArrive: function () { ctx.mark(); requestAnimationFrame(function () { requestAnimationFrame(function () { instantCopy(false); }); }); },
|
|
476
|
+
move: function (from, to, done, instant) {
|
|
477
|
+
const mine = ++pending;
|
|
478
|
+
// an instant move swaps the copy at once even when it retargets a move already under way (no onLeave then)
|
|
479
|
+
instantCopy(instant);
|
|
480
|
+
ctx.loader.begin(mine);
|
|
481
|
+
// from where the page is once the posters are ready: an earlier move may have run on while waiting
|
|
482
|
+
postersReady(world, instant ? o.instantDecodeCapMs : o.decodeCapMs, function () {
|
|
483
|
+
if (mine !== pending) return;
|
|
484
|
+
ctx.loader.end(mine);
|
|
485
|
+
const at = window.scrollY;
|
|
486
|
+
animate(at, to, instant ? 0 : moveDuration(to - at, ctx.vh(), o, reduce), done);
|
|
487
|
+
});
|
|
488
|
+
} });
|
|
365
489
|
const fine = window.matchMedia('(hover: hover) and (pointer: fine)').matches;
|
|
366
490
|
document.documentElement.classList.add('isn-on', fine ? 'isn-mouse' : 'isn-touch');
|
|
367
491
|
return ctx;
|
|
@@ -387,44 +511,75 @@
|
|
|
387
511
|
e.focus({ preventScroll: true });
|
|
388
512
|
}
|
|
389
513
|
|
|
390
|
-
//
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
//
|
|
394
|
-
|
|
514
|
+
// An instant move (Home, End, a dot) swaps the copy without the fade (class isn-instant, step-nav.css).
|
|
515
|
+
function instantCopy(on) { document.documentElement.classList.toggle('isn-instant', Boolean(on)); }
|
|
516
|
+
|
|
517
|
+
// The copy of scene k shows over the engine's own fade (class isn-copy, see step-nav.css); -1 shows none.
|
|
518
|
+
// The same element: nothing is copied, so a screen reader and the focus meet each text once.
|
|
519
|
+
function showCopy(ctx, k) {
|
|
520
|
+
ctx.world.querySelectorAll('.sw-copy').forEach(function (c, i) { c.classList.toggle('isn-copy', i === k); });
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// Classes the CSS reads: isn-last at the last scene's end; isn-ready there once its clip has played
|
|
524
|
+
// to the end (under reduced motion that stop is reached at once and the engine plays no clip);
|
|
525
|
+
// isn-body at or below the body top. At a stop or a half point the copy in view shows. Moves the
|
|
526
|
+
// focus to the body after the main button.
|
|
527
|
+
function mark(ctx) {
|
|
395
528
|
const c = document.documentElement.classList;
|
|
396
|
-
const
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
529
|
+
const y = window.scrollY;
|
|
530
|
+
const stops = ctx.stops();
|
|
531
|
+
const atLast = Math.abs(y - stops[ctx.lastStop]) <= 1;
|
|
532
|
+
const body = stops.length - 1 > ctx.lastStop && y >= stops[stops.length - 1] - 1;
|
|
533
|
+
c.toggle('isn-last', atLast);
|
|
534
|
+
c.toggle('isn-ready', Boolean(ctx.endEl()) && atLast);
|
|
535
|
+
c.toggle('isn-body', body);
|
|
536
|
+
const onGrid = gridOf(stops).some(function (v) { return Math.abs(v - y) <= 1; });
|
|
537
|
+
showCopy(ctx, onGrid && !body ? sceneAt(ctx.config, ctx.vh(), y) : -1);
|
|
538
|
+
if (ctx.focusEnd && body) focusBody(ctx);
|
|
401
539
|
}
|
|
402
540
|
|
|
403
|
-
|
|
541
|
+
// An input ({ steps }, { scenes }, { to }, End's { to: 'last' }; instant: no animation) from where the page stands.
|
|
542
|
+
function step(ctx, input) {
|
|
404
543
|
ctx.nav.sync(window.scrollY);
|
|
405
|
-
ctx.nav.input(
|
|
544
|
+
ctx.nav.input(input.to === 'last' ? { to: ctx.lastStop, instant: input.instant } : input);
|
|
406
545
|
}
|
|
407
546
|
|
|
547
|
+
// The direction an input takes from where the page (or the move in flight) is going.
|
|
548
|
+
const dirFrom = function (ctx, input) {
|
|
549
|
+
const st = ctx.nav.state();
|
|
550
|
+
return dirOf(ctx.stops(), st.moving ? st.y : window.scrollY, input.to === 'last' ? { to: ctx.lastStop, instant: input.instant } : input);
|
|
551
|
+
};
|
|
552
|
+
|
|
408
553
|
function listenWheel(ctx) {
|
|
409
554
|
const gesture = createWheelGesture(ctx.o);
|
|
555
|
+
let carry = 0;
|
|
556
|
+
let used = 0;
|
|
410
557
|
window.addEventListener('wheel', function (e) {
|
|
411
558
|
if (wheelIgnored(e)) return;
|
|
412
|
-
|
|
413
|
-
if (!ctx.ours(window.scrollY, dir)) return;
|
|
559
|
+
if (!ctx.ours(window.scrollY, e.deltaY > 0 ? 'down' : 'up')) return;
|
|
414
560
|
e.preventDefault();
|
|
415
|
-
|
|
561
|
+
const kind = gesture.feed(e, e.timeStamp);
|
|
562
|
+
if (kind === 'ignore') return;
|
|
563
|
+
if (kind === 'new') { carry = 0; used = 0; }
|
|
564
|
+
const w = wheelNotches(e, carry, ctx.o);
|
|
565
|
+
carry = w.carry;
|
|
566
|
+
const n = Math.sign(w.n) * Math.min(Math.abs(w.n), ctx.o.maxNotches - used);
|
|
567
|
+
if (!n) return;
|
|
568
|
+
used += Math.abs(n);
|
|
569
|
+
step(ctx, { steps: n });
|
|
416
570
|
}, { passive: false });
|
|
417
571
|
}
|
|
418
572
|
|
|
419
573
|
function listenKeys(ctx) {
|
|
420
574
|
window.addEventListener('keydown', function (e) {
|
|
421
|
-
const
|
|
575
|
+
const input = keyInput(e);
|
|
576
|
+
if (!input) return;
|
|
577
|
+
const dir = dirFrom(ctx, input);
|
|
422
578
|
if (!dir || !ctx.ours(window.scrollY, dir)) return;
|
|
423
579
|
e.preventDefault();
|
|
424
|
-
if (!e.repeat) step(ctx,
|
|
580
|
+
if (!e.repeat) step(ctx, input);
|
|
425
581
|
});
|
|
426
582
|
}
|
|
427
|
-
|
|
428
583
|
function listenTouch(ctx) {
|
|
429
584
|
let y0 = null;
|
|
430
585
|
window.addEventListener('touchstart', function (e) { y0 = e.touches.length === 1 ? e.touches[0].clientY : null; }, { passive: true });
|
|
@@ -438,7 +593,7 @@
|
|
|
438
593
|
const dy = y0 - e.changedTouches[0].clientY;
|
|
439
594
|
const dir = dy > 0 ? 'down' : 'up';
|
|
440
595
|
y0 = null;
|
|
441
|
-
if (Math.abs(dy) > ctx.o.swipePx && ctx.ours(window.scrollY, dir)) step(ctx, dir);
|
|
596
|
+
if (Math.abs(dy) > ctx.o.swipePx && ctx.ours(window.scrollY, dir)) step(ctx, { scenes: dir === 'down' ? 1 : -1 });
|
|
442
597
|
}, { passive: true });
|
|
443
598
|
}
|
|
444
599
|
|
|
@@ -447,10 +602,11 @@
|
|
|
447
602
|
function skip(ctx) {
|
|
448
603
|
ctx.nav.cancel();
|
|
449
604
|
stopAnimation();
|
|
605
|
+
ctx.loader.cancel();
|
|
450
606
|
const top = ctx.endTop();
|
|
451
|
-
jump(top == null ? ctx.stops()[ctx.
|
|
607
|
+
jump(top == null ? ctx.stops()[ctx.lastStop] : top);
|
|
452
608
|
ctx.nav.sync(window.scrollY);
|
|
453
|
-
ctx.mark(
|
|
609
|
+
ctx.mark();
|
|
454
610
|
focusBody(ctx);
|
|
455
611
|
}
|
|
456
612
|
|
|
@@ -473,30 +629,52 @@
|
|
|
473
629
|
if (!dot && !control) return;
|
|
474
630
|
e.preventDefault();
|
|
475
631
|
e.stopImmediatePropagation();
|
|
476
|
-
if (dot) return step(ctx, { to: Array.prototype.indexOf.call(dot.parentNode.children, dot) });
|
|
632
|
+
if (dot) return step(ctx, { to: Array.prototype.indexOf.call(dot.parentNode.children, dot), instant: true });
|
|
477
633
|
return control.classList.contains('isn-skip') ? skip(ctx) : toBody(ctx);
|
|
478
634
|
}, true);
|
|
479
635
|
}
|
|
480
636
|
|
|
481
|
-
// Scrolling by other means (scrollbar, find in page,
|
|
637
|
+
// Scrolling by other means (scrollbar, a touchpad, find in page, an anchor) moves the navigator's place
|
|
638
|
+
// too; a page that comes to rest off the stops and half points, above the body, settles on the nearest stop.
|
|
482
639
|
function listenScroll(ctx) {
|
|
483
640
|
let queued = false;
|
|
641
|
+
let rest = 0;
|
|
642
|
+
const later = function () { clearTimeout(rest); rest = setTimeout(function () { settle(ctx); }, ctx.o.snapMs); };
|
|
643
|
+
// while a pointer holds the page or its scrollbar, nothing settles; snapMs after it lets go, it may
|
|
644
|
+
window.addEventListener('pointerdown', function () { ctx.pressed = true; clearTimeout(rest); }, true);
|
|
645
|
+
// a press can end without a pointerup: a context menu, a release outside the window, a lost capture, the tab hidden
|
|
646
|
+
const release = function () { if (!ctx.pressed) return; ctx.pressed = false; later(); };
|
|
647
|
+
['pointerup', 'pointercancel', 'lostpointercapture', 'contextmenu'].forEach(function (k) { window.addEventListener(k, release, true); });
|
|
648
|
+
// Capture catches the window losing focus; a focused element inside the page losing focus (its own blur, which
|
|
649
|
+
// capture also sees) must not end a press, or a click that moves focus would clear `pressed` mid-press.
|
|
650
|
+
window.addEventListener('blur', function (e) { if (e.target === window) release(); }, true);
|
|
651
|
+
document.addEventListener('visibilitychange', function () { if (document.hidden) release(); });
|
|
484
652
|
window.addEventListener('scroll', function () {
|
|
485
|
-
if (
|
|
653
|
+
if (ctx.nav.state().moving) return;
|
|
654
|
+
later();
|
|
655
|
+
if (queued) return;
|
|
486
656
|
queued = true;
|
|
487
657
|
requestAnimationFrame(function () {
|
|
488
658
|
queued = false;
|
|
489
659
|
if (ctx.nav.state().moving) return;
|
|
490
660
|
ctx.nav.sync(window.scrollY);
|
|
491
|
-
ctx.mark(
|
|
661
|
+
ctx.mark();
|
|
492
662
|
});
|
|
493
663
|
}, { passive: true });
|
|
494
664
|
}
|
|
495
665
|
|
|
666
|
+
function settle(ctx) {
|
|
667
|
+
const y = window.scrollY;
|
|
668
|
+
if (ctx.held || ctx.pressed || ctx.nav.state().moving || !ctx.ours(y, 'down')) return;
|
|
669
|
+
const to = settleTarget(ctx.stops(), ctx.lastStop, y);
|
|
670
|
+
if (to >= 0) step(ctx, { to: to });
|
|
671
|
+
}
|
|
672
|
+
|
|
496
673
|
const api = {
|
|
497
674
|
DEFAULTS: DEFAULTS, stopPositions: stopPositions, totalWeight: totalWeight, moveDuration: moveDuration,
|
|
498
675
|
wheelIgnored: wheelIgnored, createWheelGesture: createWheelGesture, belongsToIntro: belongsToIntro,
|
|
499
|
-
createNavigator: createNavigator, keyInput: keyInput, prefetch: prefetch, attach: attach,
|
|
676
|
+
createNavigator: createNavigator, keyInput: keyInput, prefetch: prefetch, attach: attach, postersReady: postersReady,
|
|
677
|
+
gridOf: gridOf, stepFrom: stepFrom, createLoader: createLoader, sceneAt: sceneAt, wheelNotches: wheelNotches, dirOf: dirOf, settleTarget: settleTarget,
|
|
500
678
|
};
|
|
501
679
|
if (typeof module === 'object' && module.exports) module.exports = api;
|
|
502
680
|
else window.IntroStepNav = api;
|