quoxcli 0.5.0 → 0.6.1
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/CHANGELOG.md +65 -1
- package/lib/api/endpoints/admin.js +21 -0
- package/lib/api/endpoints/calendarq.js +8 -0
- package/lib/api/endpoints/governance.js +7 -0
- package/lib/api/endpoints/inboxq.js +39 -0
- package/lib/api/endpoints/index.js +4 -0
- package/lib/api/endpoints/lens.js +34 -0
- package/lib/api/endpoints/lucid.js +18 -0
- package/lib/api/endpoints/mcp.js +35 -0
- package/lib/api/endpoints/mon.js +6 -7
- package/lib/api/endpoints/notesq.js +2 -0
- package/lib/api/endpoints/quoxplan.js +51 -0
- package/lib/api/endpoints/v1.js +57 -0
- package/lib/commands/actions.js +51 -13
- package/lib/commands/admin.js +86 -0
- package/lib/commands/approvals.js +5 -1
- package/lib/commands/calendar.js +27 -0
- package/lib/commands/chat.js +1 -1
- package/lib/commands/contacts.js +2 -0
- package/lib/commands/fleet.js +164 -0
- package/lib/commands/fleetRecipe.js +174 -0
- package/lib/commands/lens.js +268 -0
- package/lib/commands/lucid.js +6 -0
- package/lib/commands/lucidGoals.js +423 -0
- package/lib/commands/mail.js +287 -7
- package/lib/commands/mcp.js +190 -0
- package/lib/commands/mon.js +14 -0
- package/lib/commands/monitoring.js +73 -1
- package/lib/commands/notesSeal.js +380 -22
- package/lib/commands/offload.js +51 -10
- package/lib/commands/plan.js +507 -54
- package/lib/commands/research.js +78 -0
- package/lib/commands/settings.js +7 -1
- package/lib/commands/volt.js +6 -1
- package/lib/commands/wazuh.js +74 -9
- package/lib/commands/wazuhIntake.js +55 -6
- package/lib/core/commandRegistrars.js +2 -0
- package/lib/core/config.js +14 -1
- package/lib/core/offload.js +83 -0
- package/lib/core/output.js +29 -4
- package/lib/core/selfUpdate.js +2 -0
- package/lib/core/version.js +15 -2
- package/lib/dragon/select.js +2 -2
- package/lib/quoxon/api.js +4 -0
- package/lib/quoxon/app.js +258 -49
- package/lib/quoxon/approvalsView.js +3 -1
- package/lib/quoxon/ask.js +4 -2
- package/lib/quoxon/bang.js +7 -1
- package/lib/quoxon/commandShort.js +1 -1
- package/lib/quoxon/commands.js +5 -5
- package/lib/quoxon/delegationDragon.js +49 -0
- package/lib/quoxon/dnd.js +47 -0
- package/lib/quoxon/dragonCompanion.js +24 -12
- package/lib/quoxon/events.js +12 -1
- package/lib/quoxon/extensions.js +2 -0
- package/lib/quoxon/foreignText.js +49 -0
- package/lib/quoxon/inboxView.js +5 -5
- package/lib/quoxon/launcher.js +2 -1
- package/lib/quoxon/loopCard.js +2 -1
- package/lib/quoxon/main.js +8 -2
- package/lib/quoxon/onboarding/dockerOffer.js +3 -1
- package/lib/quoxon/onboarding/view.js +1 -1
- package/lib/quoxon/render.js +5 -2
- package/lib/quoxon/resend.js +14 -0
- package/lib/quoxon/rows.js +3 -1
- package/lib/quoxon/secretShape.js +2 -0
- package/lib/quoxon/slash/broadcast.js +30 -6
- package/lib/quoxon/slash/broadcastRun.js +12 -6
- package/lib/quoxon/slash/cliLeaves.json +54 -0
- package/lib/quoxon/slash/dashboard.js +2 -0
- package/lib/quoxon/slash/listView.js +2 -1
- package/lib/quoxon/slash/palette.js +41 -0
- package/lib/quoxon/slash/paletteController.js +6 -2
- package/lib/quoxon/slash/restart.js +2 -0
- package/lib/quoxon/slotChat.js +7 -3
- package/lib/quoxon/slotRun.js +2 -0
- package/lib/quoxon/webPty.js +15 -0
- package/lib/quoxon/whatsnew.jsonl +7 -0
- package/lib/render/planBrief.js +614 -0
- package/lib/toolOutputShape.js +6 -3
- package/package.json +1 -1
- package/vendor/dragonlab/SOURCE +3 -3
- package/vendor/dragonlab/design/architect/v2/architect-master.svg +67 -0
- package/vendor/dragonlab/design/architect/v2/character.mjs +83 -0
- package/vendor/dragonlab/design/architect/v2/review.mjs +35 -0
- package/vendor/dragonlab/design/architect/v2/watch.mjs +56 -0
- package/vendor/dragonlab/design/archivist/v2/archivist-master.svg +77 -0
- package/vendor/dragonlab/design/archivist/v3/character.mjs +56 -0
- package/vendor/dragonlab/design/archivist/v4/character.mjs +46 -0
- package/vendor/dragonlab/design/archivist/v4/review.mjs +35 -0
- package/vendor/dragonlab/design/archivist/v4/watch.mjs +56 -0
- package/vendor/dragonlab/design/argus/v2/character.mjs +69 -0
- package/vendor/dragonlab/design/argus/v3/argus-master.svg +68 -0
- package/vendor/dragonlab/design/argus/v3/character.mjs +63 -0
- package/vendor/dragonlab/design/argus/v3/review.mjs +35 -0
- package/vendor/dragonlab/design/argus/v3/watch.mjs +56 -0
- package/vendor/dragonlab/design/daedalus/v2/character.mjs +49 -0
- package/vendor/dragonlab/design/daedalus/v3/daedalus-master.svg +65 -0
- package/vendor/dragonlab/design/daedalus/v4/character.mjs +47 -0
- package/vendor/dragonlab/design/daedalus/v5/character.mjs +66 -0
- package/vendor/dragonlab/design/daedalus/v6/character.mjs +32 -0
- package/vendor/dragonlab/design/daedalus/v6/review.mjs +35 -0
- package/vendor/dragonlab/design/daedalus/v6/watch.mjs +56 -0
- package/vendor/dragonlab/design/janus/v1/janus-master.svg +70 -0
- package/vendor/dragonlab/design/janus/v2/character.mjs +56 -0
- package/vendor/dragonlab/design/janus/v3/character.mjs +7 -0
- package/vendor/dragonlab/design/janus/v4/character.mjs +150 -0
- package/vendor/dragonlab/design/janus/v5/character.mjs +55 -0
- package/vendor/dragonlab/design/janus/v5/review.mjs +35 -0
- package/vendor/dragonlab/design/janus/v5/watch.mjs +56 -0
- package/vendor/dragonlab/design/roady/v1/roady-master.svg +67 -0
- package/vendor/dragonlab/design/roady/v2/character.mjs +69 -0
- package/vendor/dragonlab/design/roady/v3/character.mjs +91 -0
- package/vendor/dragonlab/design/roady/v3/review.mjs +35 -0
- package/vendor/dragonlab/design/roady/v3/watch.mjs +56 -0
- package/vendor/dragonlab/design/weaver/v3/weaver-master.svg +57 -0
- package/vendor/dragonlab/design/weaver/v4/character.mjs +56 -0
- package/vendor/dragonlab/design/weaver/v5/character.mjs +52 -0
- package/vendor/dragonlab/design/weaver/v6/character.mjs +88 -0
- package/vendor/dragonlab/design/weaver/v6/review.mjs +35 -0
- package/vendor/dragonlab/design/weaver/v6/watch.mjs +56 -0
- package/vendor/dragonlab/integration/companions-v1/architect.mjs +2 -0
- package/vendor/dragonlab/integration/companions-v1/archivist.mjs +2 -0
- package/vendor/dragonlab/integration/companions-v1/argus.mjs +2 -0
- package/vendor/dragonlab/integration/companions-v1/characters.mjs +86 -0
- package/vendor/dragonlab/integration/companions-v1/daedalus.mjs +2 -0
- package/vendor/dragonlab/integration/companions-v1/janus.mjs +2 -0
- package/vendor/dragonlab/integration/companions-v1/roady.mjs +2 -0
- package/vendor/dragonlab/integration/companions-v1/view.mjs +4 -1
- package/vendor/dragonlab/integration/companions-v1/weaver.mjs +2 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 480">
|
|
2
|
+
<title>Janus — two-headed keeper of the threshold</title>
|
|
3
|
+
<g id="wings">
|
|
4
|
+
<path id="left-cloak" fill="#39465c" d="M 326 284 Q 281 179 166 139 Q 191 219 143 289 Q 200 274 229 329 Q 270 304 306 351 Z"/>
|
|
5
|
+
<path id="right-cloak" fill="#474453" d="M 424 284 Q 469 179 584 139 Q 559 219 607 289 Q 550 274 521 329 Q 480 304 444 351 Z"/>
|
|
6
|
+
<path id="cloak-ribs" fill="none" stroke="#a68b61" stroke-width="8" d="M 326 284 Q 281 179 166 139 M 326 284 Q 237 213 143 289 M 326 284 L 229 329 M 424 284 Q 469 179 584 139 M 424 284 Q 513 213 607 289 M 424 284 L 521 329"/>
|
|
7
|
+
</g>
|
|
8
|
+
<g id="tail">
|
|
9
|
+
<path id="short-tail" fill="#43566a" d="M 323 353 C 251 355 219 413 158 399 Q 121 391 118 363 Q 95 401 145 422 C 221 452 267 394 331 386 Z"/>
|
|
10
|
+
<path id="tail-edge" fill="none" stroke="#719ea7" stroke-width="6" d="M 309 372 Q 224 427 162 411"/>
|
|
11
|
+
</g>
|
|
12
|
+
<g id="body">
|
|
13
|
+
<path id="broad-torso" fill="#465369" d="M 320 235 Q 375 218 430 235 L 467 330 Q 470 393 422 412 L 328 412 Q 280 393 283 330 Z"/>
|
|
14
|
+
<path id="breastplate" fill="#b29161" d="M 322 275 L 375 253 L 428 275 L 432 347 L 409 388 L 375 402 L 341 388 L 318 347 Z"/>
|
|
15
|
+
<path id="plate-inner" fill="#4c5060" d="M 334 285 L 375 269 L 416 285 L 419 340 L 399 378 L 375 388 L 351 378 L 331 340 Z"/>
|
|
16
|
+
<path id="plate-lines" fill="none" stroke="#927d60" stroke-width="6" d="M 334 307 L 375 320 L 416 307 M 331 338 L 375 351 L 419 338 M 344 366 L 375 377 L 406 366"/>
|
|
17
|
+
<path id="left-haunch" fill="#394356" d="M 299 332 Q 335 322 345 361 L 330 400 L 337 428 L 266 432 L 255 410 L 274 385 Z"/>
|
|
18
|
+
<path id="right-haunch" fill="#494958" d="M 451 332 Q 415 322 405 361 L 420 400 L 413 428 L 484 432 L 495 410 L 476 385 Z"/>
|
|
19
|
+
<path id="feet" fill="#667080" d="M 270 405 L 314 406 L 345 427 L 338 443 L 249 442 L 245 426 Z M 480 405 L 436 406 L 405 427 L 412 443 L 501 442 L 505 426 Z"/>
|
|
20
|
+
<path id="talons" fill="#e0e6e5" d="M 260 430 L 251 445 L 271 440 L 284 427 L 281 445 L 302 439 L 314 429 L 313 444 L 335 439 M 490 430 L 499 445 L 479 440 L 466 427 L 469 445 L 448 439 L 436 429 L 437 444 L 415 439"/>
|
|
21
|
+
</g>
|
|
22
|
+
<g id="necks">
|
|
23
|
+
<path id="left-neck" fill="#4b6576" d="M 263 184 L 306 166 Q 331 206 346 232 L 366 266 L 327 291 Q 307 248 283 228 Z"/>
|
|
24
|
+
<path id="right-neck" fill="#685e62" d="M 487 184 L 444 166 Q 419 206 404 232 L 384 266 L 423 291 Q 443 248 467 228 Z"/>
|
|
25
|
+
<path id="cyan-throat" fill="#64b4bb" d="M 279 193 L 296 190 Q 320 221 326 246 L 347 271 L 331 280 Q 311 243 294 225 Z"/>
|
|
26
|
+
<path id="amber-throat" fill="#d2a267" d="M 471 193 L 454 190 Q 430 221 424 246 L 403 271 L 419 280 Q 439 243 456 225 Z"/>
|
|
27
|
+
</g>
|
|
28
|
+
<g id="left-head">
|
|
29
|
+
<path id="left-skull" fill="#77828c" d="M 235 137 Q 267 115 294 137 Q 317 157 308 185 L 282 209 L 247 203 L 224 178 Z"/>
|
|
30
|
+
<path id="left-ear" fill="#536575" d="M 295 143 L 327 132 L 316 156 L 331 165 L 304 179 Z"/>
|
|
31
|
+
<path id="left-muzzle" fill="#a8b4bc" d="M 246 169 Q 211 158 190 179 L 191 195 L 236 211 L 260 193 Z"/>
|
|
32
|
+
<path id="left-mouth" fill="#080d19" d="M 192 190 Q 216 202 248 195 L 236 207 Q 209 210 193 199 Z"/>
|
|
33
|
+
<path id="left-jaw" fill="#6f7f8a" d="M 198 202 Q 218 217 243 205 L 252 218 Q 216 228 198 211 Z"/>
|
|
34
|
+
<path id="left-nostril" fill="#080d19" d="M 199 180 Q 209 175 210 186 L 200 188 Z"/>
|
|
35
|
+
<path id="left-outer-curl" fill="#eef2f2" d="M 243 147 C 206 137 202 91 228 72 C 255 53 284 75 275 99 Q 268 118 249 107 Q 268 108 264 91 C 258 73 235 79 231 96 Q 221 121 256 133 Z"/>
|
|
36
|
+
<path id="left-inner-curl" fill="#cbd5dd" d="M 285 140 C 302 122 314 98 299 84 Q 283 73 278 88 Q 275 98 286 100 Q 270 106 266 91 C 260 65 292 56 311 75 Q 339 108 303 150 Z"/>
|
|
37
|
+
<path id="left-horn-tip" fill="#526071" d="M 249 107 Q 268 118 275 99 L 264 95 Q 262 107 249 107 Z"/>
|
|
38
|
+
<path id="left-beard" fill="#e0e7e7" d="M 226 212 Q 242 222 258 211 L 271 232 L 257 228 L 259 261 L 244 244 L 235 258 L 230 235 L 220 241 Z"/>
|
|
39
|
+
</g>
|
|
40
|
+
<g id="right-head">
|
|
41
|
+
<path id="right-skull" fill="#77828c" d="M 515.0 137 Q 483.0 115 456.0 137 Q 433.0 157 442.0 185 L 468.0 209 L 503.0 203 L 526.0 178 Z"/>
|
|
42
|
+
<path id="right-ear" fill="#536575" d="M 455.0 143 L 423.0 132 L 434.0 156 L 419.0 165 L 446.0 179 Z"/>
|
|
43
|
+
<path id="right-muzzle" fill="#a8b4bc" d="M 504.0 169 Q 539.0 158 560.0 179 L 559.0 195 L 514.0 211 L 490.0 193 Z"/>
|
|
44
|
+
<path id="right-mouth" fill="#080d19" d="M 558.0 190 Q 534.0 202 502.0 195 L 514.0 207 Q 541.0 210 557.0 199 Z"/>
|
|
45
|
+
<path id="right-jaw" fill="#6f7f8a" d="M 552.0 202 Q 532.0 217 507.0 205 L 498.0 218 Q 534.0 228 552.0 211 Z"/>
|
|
46
|
+
<path id="right-nostril" fill="#080d19" d="M 551.0 180 Q 541.0 175 540.0 186 L 550.0 188 Z"/>
|
|
47
|
+
<path id="right-outer-curl" fill="#eef2f2" d="M 507.0 147 C 544.0 137 548.0 91 522.0 72 C 495.0 53 466.0 75 475.0 99 Q 482.0 118 501.0 107 Q 482.0 108 486.0 91 C 492.0 73 515.0 79 519.0 96 Q 529.0 121 494.0 133 Z"/>
|
|
48
|
+
<path id="right-inner-curl" fill="#cbd5dd" d="M 465.0 140 C 448.0 122 436.0 98 451.0 84 Q 467.0 73 472.0 88 Q 475.0 98 464.0 100 Q 480.0 106 484.0 91 C 490.0 65 458.0 56 439.0 75 Q 411.0 108 447.0 150 Z"/>
|
|
49
|
+
<path id="right-horn-tip" fill="#526071" d="M 501.0 107 Q 482.0 118 475.0 99 L 486.0 95 Q 488.0 107 501.0 107 Z"/>
|
|
50
|
+
<path id="right-beard" fill="#e0e7e7" d="M 524.0 212 Q 508.0 222 492.0 211 L 479.0 232 L 493.0 228 L 491.0 261 L 506.0 244 L 515.0 258 L 520.0 235 L 530.0 241 Z"/>
|
|
51
|
+
</g>
|
|
52
|
+
<g id="arms">
|
|
53
|
+
<path id="left-arm" fill="#61778a" d="M 313 278 Q 338 274 346 299 L 340 320 L 362 333 L 354 354 L 318 337 L 301 307 Z"/>
|
|
54
|
+
<path id="right-arm" fill="#79717a" d="M 437 278 Q 412 274 404 299 L 410 320 L 388 333 L 396 354 L 432 337 L 449 307 Z"/>
|
|
55
|
+
<path id="claws" fill="#dce6e8" d="M 348 326 L 366 320 L 362 333 L 371 336 L 357 345 L 361 353 L 345 354 Z M 402 326 L 384 320 L 388 333 L 379 336 L 393 345 L 389 353 L 405 354 Z"/>
|
|
56
|
+
</g>
|
|
57
|
+
<g id="threshold">
|
|
58
|
+
<path id="closed-seam-dark" fill="none" stroke="#080d19" stroke-width="18" d="M 375 294 L 375 375"/>
|
|
59
|
+
<path id="closed-seam-cyan" fill="none" stroke="#6fe9eb" stroke-width="4" d="M 369 294 L 369 375"/>
|
|
60
|
+
<path id="closed-seam-amber" fill="none" stroke="#f6bc6a" stroke-width="4" d="M 381 294 L 381 375"/>
|
|
61
|
+
</g>
|
|
62
|
+
<g id="eyes">
|
|
63
|
+
<path id="left-eye-socket" fill="#080d19" d="M 243 164 C 243 150.19288125 253.745166 139 267 139 C 280.254834 139 291 150.19288125 291 164 C 291 177.80711875 280.254834 189 267 189 C 253.745166 189 243 177.80711875 243 164 Z"/>
|
|
64
|
+
<path id="left-eye-white" fill="#ffffff" d="M 250 164 C 250 154.0588745 257.61115925 146 267 146 C 276.38884075 146 284 154.0588745 284 164 C 284 173.9411255 276.38884075 182 267 182 C 257.61115925 182 250 173.9411255 250 164 Z"/>
|
|
65
|
+
<path id="left-pupil" fill="#080d19" d="M 257 165 C 257 161.13400675 259.23857625 158 262 158 C 264.76142375 158 267 161.13400675 267 165 C 267 168.86599325 264.76142375 172 262 172 C 259.23857625 172 257 168.86599325 257 165 Z"/>
|
|
66
|
+
<path id="right-eye-socket" fill="#080d19" d="M 459 164 C 459 150.19288125 469.745166 139 483 139 C 496.254834 139 507 150.19288125 507 164 C 507 177.80711875 496.254834 189 483 189 C 469.745166 189 459 177.80711875 459 164 Z"/>
|
|
67
|
+
<path id="right-eye-white" fill="#ffffff" d="M 466 164 C 466 154.0588745 473.61115925 146 483 146 C 492.38884075 146 500 154.0588745 500 164 C 500 173.9411255 492.38884075 182 483 182 C 473.61115925 182 466 173.9411255 466 164 Z"/>
|
|
68
|
+
<path id="right-pupil" fill="#080d19" d="M 483 165 C 483 161.13400675 485.23857625 158 488 158 C 490.76142375 158 493 161.13400675 493 165 C 493 168.86599325 490.76142375 172 488 172 C 485.23857625 172 483 168.86599325 483 165 Z"/>
|
|
69
|
+
</g>
|
|
70
|
+
</svg>
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import {renderSvgRig} from '../../../factory/svg-rig-contour.mjs';
|
|
2
|
+
export const JANUS_CLIPS=Object.freeze([{id:'idle',title:'Watch both sides',duration:12000},{id:'attach',title:'Two guardians acknowledge',duration:3000},{id:'threshold',title:'Open the threshold',duration:12000}]);
|
|
3
|
+
export const STORY_DURATION=15000;
|
|
4
|
+
const ease=x=>{x=Math.max(0,Math.min(1,x));return x*x*(3-2*x);};
|
|
5
|
+
const pulse=(t,a,b)=>t<=a||t>=b?0:Math.sin(Math.PI*(t-a)/(b-a))**2;
|
|
6
|
+
export function poseRig(rig,scene,ms,{deck=false}={}){
|
|
7
|
+
if(scene==='threshold'&&ms>=12000)return poseRig(rig,'idle',ms-12000,{deck});
|
|
8
|
+
if(scene==='attach'&&ms>=3000)return poseRig(rig,'idle',ms-3000,{deck});
|
|
9
|
+
const t=ms%12000,active=scene==='threshold';
|
|
10
|
+
const left=active?pulse(t,0,11500):scene==='attach'?pulse(t,0,2300):pulse(t,1000,5000);
|
|
11
|
+
const right=active?pulse(t,700,11500):scene==='attach'?pulse(t,600,3000):pulse(t,6500,10500);
|
|
12
|
+
const opening=active?ease((t-3000)/2200)*(1-ease((t-8500)/2500)):0;
|
|
13
|
+
const move=(x,y,side,w=1)=>{const v=side==='left'?left:right,dir=side==='left'?1:-1;return [x+dir*(active?8:-3)*v*w,y+(active?6:3)*v*w];};
|
|
14
|
+
const paths=rig.paths.filter(p=>p.group!=='threshold').map(p=>({...p,polygons:p.polygons.map(poly=>poly.map(([x,y])=>{
|
|
15
|
+
if(p.group==='left-head'||p.group==='right-head')return move(x,y,p.group.split('-')[0]);
|
|
16
|
+
if(p.group==='eyes')return move(x,y,p.id.split('-')[0]);
|
|
17
|
+
if(p.group==='necks')return move(x,y,x<375?'left':'right',Math.max(0,Math.min(1,(280-y)/85)));
|
|
18
|
+
if(p.group==='arms'){const w=Math.max(0,Math.min(1,(y-285)/45));return [x+(x<375?-1:1)*30*opening*w,y];}
|
|
19
|
+
return [x,y];
|
|
20
|
+
}))}));
|
|
21
|
+
const add=(id,rgb,polygons,stroke=3,erase=false)=>paths.push({id,group:'effect',rgb,polygons,stroke,erase});
|
|
22
|
+
const half=6+40*opening,top=294-14*opening,bottom=375+14*opening;
|
|
23
|
+
add('gateway-void',[8,13,25],[[[375-half,top],[375+half,top],[375+half,bottom],[375-half,bottom]]],0,true);
|
|
24
|
+
add('cyan-jamb',[111,233,235],[[[375-half,top],[375-half,bottom]]],4);
|
|
25
|
+
add('amber-jamb',[246,188,106],[[[375+half,top],[375+half,bottom]]],4);
|
|
26
|
+
if(opening>.01){
|
|
27
|
+
add('gate-lintel',[147,191,209],[[[375-half,top],[375,top-8*opening],[375+half,top]],[[375-half,bottom],[375,bottom+8*opening],[375+half,bottom]]],3);
|
|
28
|
+
if(deck)for(let i=1;i<=3;i++){
|
|
29
|
+
const inset=i*7*opening,h=half-inset;
|
|
30
|
+
add('passage-depth-'+i,[32+i*11,71+i*9,95+i*11],[[[375-h,top+inset],[375+h,top+inset],[375+h,bottom-inset],[375-h,bottom-inset],[375-h,top+inset]]],2);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if(active&&t>800&&t<8300){
|
|
34
|
+
const approach=ease((t-800)/1400),transit=ease((t-5500)/2200);
|
|
35
|
+
const x=t<5500?275+40*approach:315+120*transit;
|
|
36
|
+
const size=5*ease((t-800)/300)*(1-ease((t-7800)/500));
|
|
37
|
+
const c=t<5500?[243,194,109]:[195,255,245];
|
|
38
|
+
add('traveller',c,[[[x-size,335],[x,335-size],[x+size,335],[x,335+size]]],0);
|
|
39
|
+
}
|
|
40
|
+
const map=new Map(paths.map(p=>[p.id,p]));
|
|
41
|
+
return {...rig,paths,eyes:rig.eyes.map(e=>({...e,socket:map.get(e.socket.id),white:map.get(e.white.id),pupil:map.get(e.pupil.id)}))};
|
|
42
|
+
}
|
|
43
|
+
export function renderJanus(rig,scene='idle',ms=0,options={}){
|
|
44
|
+
if(!JANUS_CLIPS.some(c=>c.id===scene))return {ok:false,code:'UNKNOWN_CLIP'};
|
|
45
|
+
if(!Number.isFinite(ms)||ms<0)return {ok:false,code:'INVALID_TIME'};
|
|
46
|
+
if(!options.still&&scene==='threshold'&&ms>=12000)return renderJanus(rig,'idle',ms-12000,options);
|
|
47
|
+
if(!options.still&&scene==='attach'&&ms>=3000)return renderJanus(rig,'idle',ms-3000,options);
|
|
48
|
+
const t=options.still?({idle:0,attach:1500,threshold:6500}[scene]):ms;
|
|
49
|
+
const caption=scene==='threshold'?(t<2200?'An arrival at the gate.':t<3000?'Wait at the threshold.':t<5500?'Both guardians attend. Open the way.':t<8300?'Pass through the gateway.':'Close the passage. Resume watch.'):scene==='attach'?'Both sides acknowledged.':'One watches each side.';
|
|
50
|
+
return {...renderSvgRig(poseRig(rig,scene,t,options),options),scene,clip:scene,caption};
|
|
51
|
+
}
|
|
52
|
+
export function renderStory(rig,ms,options={}){
|
|
53
|
+
if(!Number.isFinite(ms)||ms<0)return {ok:false,code:'INVALID_TIME'};
|
|
54
|
+
if(options.still)return renderJanus(rig,'threshold',6500,options);
|
|
55
|
+
return ms<3000?renderJanus(rig,'attach',ms,options):renderJanus(rig,'threshold',ms-3000,options);
|
|
56
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import {poseRig as pose,renderJanus as render,renderStory as story} from '../v2/character.mjs';
|
|
2
|
+
export {JANUS_CLIPS,STORY_DURATION} from '../v2/character.mjs';
|
|
3
|
+
// Preserve V2 camera framing while lowering and separating the horns.
|
|
4
|
+
const framed=rig=>({...rig,bounds:{left:110.7777777777778,top:64.82150205761317,right:607,bottom:445}});
|
|
5
|
+
export const poseRig=(rig,...args)=>pose(framed(rig),...args);
|
|
6
|
+
export const renderJanus=(rig,...args)=>render(framed(rig),...args);
|
|
7
|
+
export const renderStory=(rig,...args)=>story(framed(rig),...args);
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import {renderSvgRig} from '../../../factory/svg-rig-contour.mjs';
|
|
2
|
+
import {poseRig as v3Pose,renderJanus as v3Render} from '../v3/character.mjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Janus V4: gate acting on the approved V1 body and the V3 horns (review pending).
|
|
6
|
+
* Idle and Threshold are V3 exactly. V4 adds a compact attach entrance (notice, acknowledge,
|
|
7
|
+
* settle), Hold (gate shut and barred while the host reports a pending approval) and Deny
|
|
8
|
+
* (an offline performance: a traveller is turned back from a barred gate).
|
|
9
|
+
* Pure functions of elapsed time. Renderers draw; the host supplies approval state.
|
|
10
|
+
*/
|
|
11
|
+
export const JANUS_CLIPS=Object.freeze([
|
|
12
|
+
{id:'idle',title:'Watch both sides',duration:12000},
|
|
13
|
+
{id:'attach',title:'Notice, acknowledge, settle',duration:3600},
|
|
14
|
+
{id:'threshold',title:'Open the threshold',duration:12000},
|
|
15
|
+
{id:'hold',title:'Hold the gate',duration:9200},
|
|
16
|
+
{id:'grant',title:'Unbar and open',duration:1400},
|
|
17
|
+
{id:'deny',title:'Turn back at the gate',duration:6400},
|
|
18
|
+
]);
|
|
19
|
+
export const ATTACH_MS=3600,HOLD_ENTER_MS=1200,HOLD_LOOP_MS=8000,GRANT_MS=1400,DENY_MS=6400;
|
|
20
|
+
export const STORY_DURATION=ATTACH_MS+HOLD_ENTER_MS+HOLD_LOOP_MS+GRANT_MS+12000+DENY_MS;
|
|
21
|
+
const STILL=Object.freeze({idle:0,attach:1500,threshold:6500,hold:3000,grant:700,deny:2600});
|
|
22
|
+
// V3 camera, held fixed for every V4 clip so nothing in the frame breathes.
|
|
23
|
+
const framed=rig=>({...rig,bounds:{left:110.7777777777778,top:64.82150205761317,right:607,bottom:445}});
|
|
24
|
+
const clamp=x=>Math.max(0,Math.min(1,x));
|
|
25
|
+
const ease=x=>{x=clamp(x);return x*x*(3-2*x);};
|
|
26
|
+
const pulse=(t,a,b)=>t<=a||t>=b?0:Math.sin(Math.PI*(t-a)/(b-a))**2;
|
|
27
|
+
const mix=(a,b,k)=>a+(b-a)*k;
|
|
28
|
+
|
|
29
|
+
/** One posed parameter set; every V4 clip is a timeline of these. Rest = V3 idle at t=0. */
|
|
30
|
+
const REST=Object.freeze({lx:0,ly:0,rx:0,ry:0,lg:0,rg:0,cloak:0,door:0,drop:0,glow:0,key:0,flash:0,traveller:null});
|
|
31
|
+
export function janusParams(scene,t){
|
|
32
|
+
if(scene==='attach'){
|
|
33
|
+
// Notice: the outer (cyan, left) face lifts first, the inner (amber, right) one follows.
|
|
34
|
+
const lift=-4*pulse(t,0,1700),liftR=-4*pulse(t,250,1950);
|
|
35
|
+
// Acknowledge: both faces dip toward the gate together and glance back at whoever arrived.
|
|
36
|
+
const nod=pulse(t,900,2700),look=ease((t-350)/550)*(1-ease((t-2300)/900));
|
|
37
|
+
return {...REST,lx:7*nod,ly:lift+7*nod,rx:-7*nod,ry:liftR+7*nod,lg:7*look,rg:-7*look,
|
|
38
|
+
// Cloak tips follow the nod late and settle last; the keystone answers the nod.
|
|
39
|
+
cloak:-5*pulse(t,1250,3500),glow:pulse(t,900,2900),key:pulse(t,1000,3100)};
|
|
40
|
+
}
|
|
41
|
+
if(scene==='hold'){
|
|
42
|
+
// The doorway darkens, then the portcullis drops from the lintel: the gate visibly closes.
|
|
43
|
+
const shut=ease(t/HOLD_ENTER_MS),drop=ease((t-200)/(HOLD_ENTER_MS-200));
|
|
44
|
+
const u=Math.max(0,t-HOLD_ENTER_MS)%HOLD_LOOP_MS;
|
|
45
|
+
// Patient watch while shut: one face keeps the gate, the other checks the fleet, then they swap.
|
|
46
|
+
const outL=pulse(u,600,3400),outR=pulse(u,4600,7400),breathe=Math.sin(Math.PI*u/HOLD_LOOP_MS)**2;
|
|
47
|
+
return {...REST,lx:10*shut-6*outL,ly:7*shut+breathe,rx:-10*shut+6*outR,ry:7*shut+breathe,
|
|
48
|
+
lg:7*shut*(1-outL)-3*outL,rg:-7*shut*(1-outR)+3*outR,door:shut,drop,glow:.5*shut,key:shut};
|
|
49
|
+
}
|
|
50
|
+
if(scene==='grant'){
|
|
51
|
+
// Starts from the shut Hold pose (loop phase zero): portcullis rises first, then the doorway and faces return to rest.
|
|
52
|
+
const k=1-ease(t/GRANT_MS),up=1-ease(t/(GRANT_MS*.6));
|
|
53
|
+
return {...REST,lx:10*k,ly:7*k,rx:-10*k,ry:7*k,lg:7*k,rg:-7*k,door:k,drop:up,glow:.5*k+.5*pulse(t,0,GRANT_MS),key:k};
|
|
54
|
+
}
|
|
55
|
+
if(scene==='deny'){
|
|
56
|
+
const turn=ease(t/700)*(1-ease((t-4600)/1400));
|
|
57
|
+
// The portcullis slams, the bars flash when the traveller meets them, and it is turned back.
|
|
58
|
+
const drop=ease((t-500)/350)*(1-ease((t-4400)/1100)),flash=pulse(t,1850,2750),rear=-4*pulse(t,1850,3000);
|
|
59
|
+
let traveller=null;
|
|
60
|
+
if(t>300&&t<4400){
|
|
61
|
+
const stop=375-(6+12*turn)-19,x=t<1900?mix(232,stop,ease((t-300)/1600)):t<2500?stop:mix(stop,232,ease((t-2500)/1600));
|
|
62
|
+
const size=11*ease((t-300)/300)*(1-ease((t-3800)/600));
|
|
63
|
+
traveller={x,size,rgb:t<2500?[243,194,109]:[226,120,96]};
|
|
64
|
+
}
|
|
65
|
+
return {...REST,lx:10*turn,ly:7*turn+rear,rx:-10*turn,ry:7*turn+rear,lg:7*turn,rg:-7*turn,door:turn,drop,glow:turn,key:turn,flash,traveller};
|
|
66
|
+
}
|
|
67
|
+
return REST;
|
|
68
|
+
}
|
|
69
|
+
export const BAR_Y=Object.freeze([308,330,352]);
|
|
70
|
+
function poseFromParams(rig,p,{deck=false}={}){
|
|
71
|
+
const moveHead=(x,y,side)=>side==='left'?[x+p.lx,y+p.ly]:[x+p.rx,y+p.ry];
|
|
72
|
+
const paths=rig.paths.filter(q=>q.group!=='threshold').map(q=>({...q,polygons:q.polygons.map(poly=>poly.map(([x,y])=>{
|
|
73
|
+
if(q.group==='left-head'||q.group==='right-head')return moveHead(x,y,q.group.split('-')[0]);
|
|
74
|
+
if(q.group==='eyes'){
|
|
75
|
+
const side=q.id.split('-')[0],[hx,hy]=moveHead(x,y,side);
|
|
76
|
+
// Gaze: only the pupil slides, staying inside the white. Positive = toward the gate.
|
|
77
|
+
return q.id.endsWith('-pupil')?[hx+(side==='left'?p.lg:p.rg),hy]:[hx,hy];
|
|
78
|
+
}
|
|
79
|
+
// Blend the neck into the chest so the faces never detach from the body.
|
|
80
|
+
if(q.group==='necks'){const w=clamp((280-y)/85),side=x<375?'left':'right',[hx,hy]=moveHead(x,y,side);return [mix(x,hx,w),mix(y,hy,w)];}
|
|
81
|
+
// Cloak roots pinned at the shoulders; distal tips lift with distance.
|
|
82
|
+
if(q.group==='wings'){const w=clamp(Math.abs(x-375)/230);return [x,y+p.cloak*w];}
|
|
83
|
+
return [x,y];
|
|
84
|
+
}))}));
|
|
85
|
+
const add=(id,rgb,polygons,stroke=3,erase=false)=>paths.push({id,group:'effect',rgb,polygons,stroke,erase});
|
|
86
|
+
const lerp=(a,b,k)=>a.map((v,i)=>Math.round(mix(v,b[i],k)));
|
|
87
|
+
// At rest these three paths are exactly V3's closed seam.
|
|
88
|
+
const half=6+12*p.door,top=294-8*p.door,bottom=375+3*p.door;
|
|
89
|
+
add('gateway-void',[8,13,25],[[[375-half,top],[375+half,top],[375+half,bottom],[375-half,bottom]]],0,true);
|
|
90
|
+
add('cyan-jamb',lerp([111,233,235],[190,255,252],p.glow),[[[375-half,top],[375-half,bottom]]],4);
|
|
91
|
+
add('amber-jamb',lerp([246,188,106],[255,226,160],p.glow),[[[375+half,top],[375+half,bottom]]],4);
|
|
92
|
+
if(p.drop>.02){
|
|
93
|
+
// Portcullis: the lattice slides down from the lintel; only bars already inside the doorway are drawn.
|
|
94
|
+
const fall=(1-p.drop)*(BAR_Y[2]-top+6),rgb=lerp([232,238,240],[255,138,100],p.flash);
|
|
95
|
+
BAR_Y.forEach((y0,i)=>{const y=y0-fall;if(y<top+4)return;
|
|
96
|
+
add('gate-bar-'+i+'-gap',[8,13,25],[[[375-half+3,y-5],[375+half-3,y-5],[375+half-3,y+5],[375-half+3,y+5]]],0,true);
|
|
97
|
+
add('gate-bar-'+i,rgb,[[[375-half+3,y],[375+half-3,y]]],6);});
|
|
98
|
+
}
|
|
99
|
+
if(p.key>.05){
|
|
100
|
+
// Keystone: the gate's own mark, a wedge on an erased halo just above the doorway.
|
|
101
|
+
const k=p.key,w=6+6*k,h=16*k,y=top-3;
|
|
102
|
+
add('keystone-halo',[8,13,25],[[[375-w-4,y-h-4],[375+w+4,y-h-4],[375+w*.55+3,y+2],[375-w*.55-3,y+2]]],0,true);
|
|
103
|
+
add('keystone',lerp([150,160,170],[246,226,170],k),[[[375-w,y-h],[375+w,y-h],[375+w*.55,y],[375-w*.55,y]]],0);
|
|
104
|
+
}
|
|
105
|
+
if(p.traveller&&p.traveller.size>.5){
|
|
106
|
+
const {x,size,rgb}=p.traveller,y=335,d=size+5;
|
|
107
|
+
add('traveller-halo',[8,13,25],[[[x-d,y],[x,y-d],[x+d,y],[x,y+d]]],0,true);
|
|
108
|
+
add('traveller',rgb,[[[x-size,y],[x,y-size],[x+size,y],[x,y+size]]],0);
|
|
109
|
+
}
|
|
110
|
+
const map=new Map(paths.map(q=>[q.id,q]));
|
|
111
|
+
return {...rig,paths,eyes:rig.eyes.map(e=>({...e,socket:map.get(e.socket.id),white:map.get(e.white.id),pupil:map.get(e.pupil.id)}))};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const V4_SCENES=new Set(['attach','hold','grant','deny']);
|
|
115
|
+
export function poseRig(rig,scene,ms,options={}){
|
|
116
|
+
if(!V4_SCENES.has(scene))return v3Pose(rig,scene,ms,options);
|
|
117
|
+
if(scene==='attach'&&ms>=ATTACH_MS)return v3Pose(rig,'idle',ms-ATTACH_MS,options);
|
|
118
|
+
if(scene==='grant'&&ms>=GRANT_MS)return v3Pose(rig,'idle',ms-GRANT_MS,options);
|
|
119
|
+
if(scene==='deny'&&ms>=DENY_MS)return v3Pose(rig,'idle',ms-DENY_MS,options);
|
|
120
|
+
return poseFromParams(framed(rig),janusParams(scene,ms),options);
|
|
121
|
+
}
|
|
122
|
+
function caption(scene,t){
|
|
123
|
+
if(scene==='attach')return t<900?'Someone at the gate.':t<2300?'Both faces acknowledge you.':'Settle. Watch both ways.';
|
|
124
|
+
if(scene==='hold')return t<HOLD_ENTER_MS?'Shut the gate.':'Gate held until you decide.';
|
|
125
|
+
if(scene==='grant')return 'Approved. Unbar the gate.';
|
|
126
|
+
if(scene==='deny')return t<700?'Face the gate.':t<2600?'Bar the way.':t<4400?'Turned back.':'Resume watch.';
|
|
127
|
+
return '';
|
|
128
|
+
}
|
|
129
|
+
export function renderJanus(rig,scene='idle',ms=0,options={}){
|
|
130
|
+
if(!JANUS_CLIPS.some(c=>c.id===scene))return {ok:false,code:'UNKNOWN_CLIP'};
|
|
131
|
+
if(!Number.isFinite(ms)||ms<0)return {ok:false,code:'INVALID_TIME'};
|
|
132
|
+
if(!V4_SCENES.has(scene))return v3Render(rig,scene,ms,options);
|
|
133
|
+
// Still freezes every channel (heads, pupils, cloak, keystone, bars, traveller) at one pose.
|
|
134
|
+
const t=options.still?STILL[scene]:ms;
|
|
135
|
+
if(scene==='attach'&&t>=ATTACH_MS)return v3Render(rig,'idle',t-ATTACH_MS,options);
|
|
136
|
+
if(scene==='grant'&&t>=GRANT_MS)return v3Render(rig,'idle',t-GRANT_MS,options);
|
|
137
|
+
if(scene==='deny'&&t>=DENY_MS)return v3Render(rig,'idle',t-DENY_MS,options);
|
|
138
|
+
return {...renderSvgRig(poseRig(rig,scene,t,options),options),scene,clip:scene,caption:caption(scene,t)};
|
|
139
|
+
}
|
|
140
|
+
/** Offline story: attach, a pending approval holds the gate, approval unbars it, a traveller passes, then a denial. Simulated; no host state. */
|
|
141
|
+
export function renderStory(rig,ms,options={}){
|
|
142
|
+
if(!Number.isFinite(ms)||ms<0)return {ok:false,code:'INVALID_TIME'};
|
|
143
|
+
if(options.still)return renderJanus(rig,'hold',0,options);
|
|
144
|
+
const holdEnd=ATTACH_MS+HOLD_ENTER_MS+HOLD_LOOP_MS,grantEnd=holdEnd+GRANT_MS,openEnd=grantEnd+12000;
|
|
145
|
+
if(ms<ATTACH_MS)return renderJanus(rig,'attach',ms,options);
|
|
146
|
+
if(ms<holdEnd)return renderJanus(rig,'hold',ms-ATTACH_MS,options);
|
|
147
|
+
if(ms<grantEnd)return renderJanus(rig,'grant',ms-holdEnd,options);
|
|
148
|
+
if(ms<openEnd)return renderJanus(rig,'threshold',ms-grantEnd,options);
|
|
149
|
+
return renderJanus(rig,'deny',Math.min(ms-openEnd,DENY_MS-1),options);
|
|
150
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import {renderSvgRig} from '../../../factory/svg-rig-contour.mjs';
|
|
2
|
+
import {poseRig as v4Pose,JANUS_CLIPS,ATTACH_MS,GRANT_MS,DENY_MS,HOLD_ENTER_MS,HOLD_LOOP_MS,STORY_DURATION,janusParams,BAR_Y} from '../v4/character.mjs';
|
|
3
|
+
export {JANUS_CLIPS,ATTACH_MS,GRANT_MS,DENY_MS,HOLD_ENTER_MS,HOLD_LOOP_MS,STORY_DURATION,janusParams,BAR_Y};
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Janus V5: the APPROVED V1 look (design/janus/v1/janus-master.svg: anatomy, curled horns, palette,
|
|
7
|
+
* face) driven by V4's acting (attach, idle, hold, grant, threshold, deny). Load it with the V1 master.
|
|
8
|
+
* V4 acted on V3's lower ram horns, which the owner never approved; V5 drops them.
|
|
9
|
+
* V4's camera bounds already equal the V1 master's ink bounds, so framing is V1's.
|
|
10
|
+
* One visual repair: V2 to V4 replaced V1's authored closed seam (an 18-wide dark stroke between the
|
|
11
|
+
* two jambs) with a narrower erased doorway. V5 puts V1's seam back, following the doorway's top and
|
|
12
|
+
* bottom, so the rest pose is cell-for-cell V1 and the opened doorway still erases over it.
|
|
13
|
+
* Pure functions of elapsed time. Renderers draw; the host supplies approval state.
|
|
14
|
+
*/
|
|
15
|
+
const STILL=Object.freeze({idle:0,attach:1500,threshold:6500,hold:3000,grant:700,deny:2600});
|
|
16
|
+
const V1_SEAM={id:'closed-seam-dark',group:'threshold',rgb:[8,13,25],stroke:18,erase:true};
|
|
17
|
+
|
|
18
|
+
function restoreV1Seam(posed){
|
|
19
|
+
const at=posed.paths.findIndex(p=>p.id==='gateway-void');
|
|
20
|
+
if(at<0)return posed;
|
|
21
|
+
const ys=posed.paths[at].polygons[0].map(([,y])=>y),top=Math.min(...ys),bottom=Math.max(...ys);
|
|
22
|
+
const paths=[...posed.paths.slice(0,at),{...V1_SEAM,polygons:[[[375,top],[375,bottom]]]},...posed.paths.slice(at)];
|
|
23
|
+
return {...posed,paths};
|
|
24
|
+
}
|
|
25
|
+
export function poseRig(rig,scene,ms,options={}){return restoreV1Seam(v4Pose(rig,scene,ms,options));}
|
|
26
|
+
|
|
27
|
+
function caption(scene,t){
|
|
28
|
+
if(scene==='attach')return t<900?'Someone at the gate.':t<2300?'Both faces acknowledge you.':'Settle. Watch both ways.';
|
|
29
|
+
if(scene==='hold')return t<HOLD_ENTER_MS?'Shut the gate.':'Gate held until you decide.';
|
|
30
|
+
if(scene==='grant')return 'Approved. Unbar the gate.';
|
|
31
|
+
if(scene==='deny')return t<700?'Face the gate.':t<2600?'Bar the way.':t<4400?'Turned back.':'Resume watch.';
|
|
32
|
+
if(scene==='threshold')return t<2200?'An arrival at the gate.':t<3000?'Wait at the threshold.':t<5500?'Both guardians attend. Open the way.':t<8300?'Pass through the gateway.':'Close the passage. Resume watch.';
|
|
33
|
+
return 'One watches each side.';
|
|
34
|
+
}
|
|
35
|
+
const ENDS={attach:ATTACH_MS,grant:GRANT_MS,deny:DENY_MS,threshold:12000};
|
|
36
|
+
export function renderJanus(rig,scene='idle',ms=0,options={}){
|
|
37
|
+
if(!JANUS_CLIPS.some(c=>c.id===scene))return {ok:false,code:'UNKNOWN_CLIP'};
|
|
38
|
+
if(!Number.isFinite(ms)||ms<0)return {ok:false,code:'INVALID_TIME'};
|
|
39
|
+
// Still freezes every channel (heads, pupils, cloak, keystone, bars, traveller) at one pose.
|
|
40
|
+
let t=options.still?STILL[scene]:ms;
|
|
41
|
+
// Clips that hand off to idle continue on idle's clock.
|
|
42
|
+
if(ENDS[scene]&&t>=ENDS[scene]){t-=ENDS[scene];scene='idle';}
|
|
43
|
+
return {...renderSvgRig(poseRig(rig,scene,t,options),options),scene,clip:scene,caption:caption(scene,t)};
|
|
44
|
+
}
|
|
45
|
+
/** Offline story: attach, a pending approval holds the gate, approval unbars it, a traveller passes, then a denial. Simulated; no host state. */
|
|
46
|
+
export function renderStory(rig,ms,options={}){
|
|
47
|
+
if(!Number.isFinite(ms)||ms<0)return {ok:false,code:'INVALID_TIME'};
|
|
48
|
+
if(options.still)return renderJanus(rig,'hold',0,options);
|
|
49
|
+
const holdEnd=ATTACH_MS+HOLD_ENTER_MS+HOLD_LOOP_MS,grantEnd=holdEnd+GRANT_MS,openEnd=grantEnd+12000;
|
|
50
|
+
if(ms<ATTACH_MS)return renderJanus(rig,'attach',ms,options);
|
|
51
|
+
if(ms<holdEnd)return renderJanus(rig,'hold',ms-ATTACH_MS,options);
|
|
52
|
+
if(ms<grantEnd)return renderJanus(rig,'grant',ms-holdEnd,options);
|
|
53
|
+
if(ms<openEnd)return renderJanus(rig,'threshold',ms-grantEnd,options);
|
|
54
|
+
return renderJanus(rig,'deny',Math.min(ms-openEnd,DENY_MS-1),options);
|
|
55
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import {parseSvgRig} from '../../../factory/svg-rig-contour.mjs';
|
|
4
|
+
import {renderJanus,JANUS_CLIPS} from './character.mjs';
|
|
5
|
+
import {artSlot,composeMessage} from '../../lucid/recovery/layout.mjs';
|
|
6
|
+
import {cellsToAnsi} from '../../../vendor/quox-dragon/qart.js';
|
|
7
|
+
const args=process.argv.slice(2),values=new Set(['--cols','--rows','--terminal-cols','--terminal-rows','--message','--scene','--at']);
|
|
8
|
+
for(let i=0;i<args.length;i++){
|
|
9
|
+
if(values.has(args[i])){if(!args[i+1]||args[i+1].startsWith('--'))throw Error('Missing option value');i++;}
|
|
10
|
+
else if(!['--large','--mono','--help','--watch','--attach','--intro','--still','--list'].includes(args[i]))throw Error('Unknown option: '+args[i]);
|
|
11
|
+
}
|
|
12
|
+
const value=(name,fallback)=>args.includes(name)?args[args.indexOf(name)+1]:fallback;
|
|
13
|
+
if(args.includes('--help')){console.log('Janus V5 gate studio (approved V1 look, V4 acting). --watch --attach --intro --still --scene ID --at MS --list --large --mono --message TEXT --cols N --rows N --terminal-cols N --terminal-rows N');process.exit(0);}
|
|
14
|
+
if(args.includes('--list')){console.log(JANUS_CLIPS.map(c=>c.id+' · '+c.title).join('\n'));process.exit(0);}
|
|
15
|
+
const w=Number(value('--terminal-cols',process.stdout.columns||80)),h=Number(value('--terminal-rows',process.stdout.rows||24));
|
|
16
|
+
if(!Number.isSafeInteger(w)||!Number.isSafeInteger(h)||w<1||h<1)throw Error('Invalid terminal size');
|
|
17
|
+
const slot=artSlot(w,h),large=args.includes('--large')||args.includes('--intro'),explicit=large||args.includes('--cols')||args.includes('--rows');
|
|
18
|
+
const cols=Number(value('--cols',large?Math.min(120,w-4):Math.min(39,slot.cols))),rows=Number(value('--rows',large?Math.min(36,h-4):Math.min(11,slot.rows)));
|
|
19
|
+
const message=value('--message','JANUS\nWatch. Judge. Open.');
|
|
20
|
+
if(!slot.showArt&&!explicit){console.log(composeMessage(null,message,{terminalCols:w}).join('\n'));}
|
|
21
|
+
else{
|
|
22
|
+
if(cols>w-4||rows>h-2)throw Error('Requested art exceeds terminal margins');
|
|
23
|
+
const rig=parseSvgRig(await fs.readFile(new URL('../v1/janus-master.svg',import.meta.url),'utf8'));
|
|
24
|
+
const opts={cols,rows,mono:args.includes('--mono'),still:args.includes('--still'),deck:large};
|
|
25
|
+
const scene=value('--scene',args.includes('--attach')?'attach':'idle');
|
|
26
|
+
const r=renderJanus(rig,scene,Number(value('--at',0)),opts);
|
|
27
|
+
if(!r.ok)throw Error(r.code);
|
|
28
|
+
if(args.includes('--watch')||args.includes('--attach')||args.includes('--intro')){
|
|
29
|
+
const {watch}=await import('./watch.mjs');
|
|
30
|
+
await watch(rig,opts,{terminalCols:w,terminalRows:h,large,scene:args.includes('--scene')||args.includes('--attach')?scene:null,arrival:args.includes('--attach'),message:args.includes('--message')?message:null});
|
|
31
|
+
}else{
|
|
32
|
+
const shownMessage=args.includes('--message')?message:'JANUS\n'+r.caption;
|
|
33
|
+
console.log((large?[...cellsToAnsi(r.frame),...composeMessage(null,shownMessage,{terminalCols:w})]:composeMessage(r.frame,shownMessage,{terminalCols:w})).join('\n'));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import {performance} from 'node:perf_hooks';
|
|
2
|
+
import {JANUS_CLIPS,renderJanus,renderStory,STORY_DURATION} from './character.mjs';
|
|
3
|
+
import {composeMessage} from '../../lucid/recovery/layout.mjs';
|
|
4
|
+
import {cellsToAnsi} from '../../../vendor/quox-dragon/qart.js';
|
|
5
|
+
|
|
6
|
+
/** One bounded performance. No alternate screen or full-screen clearing. */
|
|
7
|
+
export async function watch(paths,options,config){
|
|
8
|
+
if(!process.stdout.isTTY||!process.stdin.isTTY)throw new Error('--watch/--intro needs an interactive terminal. Use --scene and --at for snapshots.');
|
|
9
|
+
const {terminalCols,terminalRows,scene,large,message}=config;
|
|
10
|
+
const clip=scene?JANUS_CLIPS.find(c=>c.id===scene):null;
|
|
11
|
+
const duration=(clip?.duration??STORY_DURATION)+(config.arrival?6000:0);
|
|
12
|
+
const realCols=process.stdout.columns,realRows=process.stdout.rows;
|
|
13
|
+
const start=performance.now();
|
|
14
|
+
const render=t=>{
|
|
15
|
+
const r=scene?renderJanus(paths,scene,t,options):renderStory(paths,t,options);
|
|
16
|
+
if(!r?.ok)throw new Error(r?.code??'Invalid story frame');
|
|
17
|
+
const caption=message??'JANUS · '+r.clip+'\n'+r.caption;
|
|
18
|
+
return large?[...cellsToAnsi(r.frame),...caption.split('\n')]:composeMessage(r.frame,caption,{terminalCols});
|
|
19
|
+
};
|
|
20
|
+
// Reserve enough lines for captions too; keep within the real terminal.
|
|
21
|
+
const height=options.rows+3;
|
|
22
|
+
if(height>=terminalRows)throw new Error('Animation needs room for its caption; reduce --rows.');
|
|
23
|
+
let ended=false,drawn=false,timer,resolveDone,rejectDone;
|
|
24
|
+
const wasRaw=Boolean(process.stdin.isRaw);
|
|
25
|
+
const done=new Promise((resolve,reject)=>{resolveDone=resolve;rejectDone=reject;});
|
|
26
|
+
const cleanup=(error)=>{
|
|
27
|
+
if(ended)return;ended=true;clearInterval(timer);
|
|
28
|
+
process.stdin.off('data',key);process.stdout.off('resize',resized);
|
|
29
|
+
process.off('SIGINT',sigint);process.off('SIGTERM',sigterm);
|
|
30
|
+
process.stdin.setRawMode(wasRaw);process.stdin.pause();
|
|
31
|
+
if(drawn)process.stdout.write(`\x1b[${height}B\r`);
|
|
32
|
+
process.stdout.write('\x1b[0m\x1b[?25h');
|
|
33
|
+
error?rejectDone(error):resolveDone();
|
|
34
|
+
};
|
|
35
|
+
const key=data=>{if(data.includes(3)||data.toString().includes('q'))cleanup();};
|
|
36
|
+
const sigint=()=>cleanup(),sigterm=()=>cleanup();
|
|
37
|
+
const resized=()=>cleanup(new Error('Terminal resized; rerun for the new art slot.'));
|
|
38
|
+
try{
|
|
39
|
+
process.stdin.setRawMode(true);process.stdin.resume();process.stdin.on('data',key);
|
|
40
|
+
process.stdout.on('resize',resized);process.on('SIGINT',sigint);process.on('SIGTERM',sigterm);
|
|
41
|
+
process.stdout.write('\x1b[?25l');
|
|
42
|
+
// Allocate the block once; each frame replaces only these lines.
|
|
43
|
+
process.stdout.write('\n'.repeat(height)+`\x1b[${height}A`);drawn=true;
|
|
44
|
+
const tick=()=>{
|
|
45
|
+
try{
|
|
46
|
+
if(process.stdout.columns!==realCols||process.stdout.rows!==realRows)return resized();
|
|
47
|
+
const t=Math.min(performance.now()-start,duration-1),lines=render(t);
|
|
48
|
+
if(lines.length>height)throw new Error('Message exceeds the animation block; use a shorter message or a static preview.');
|
|
49
|
+
process.stdout.write(Array.from({length:height},(_,i)=>'\r\x1b[2K'+(lines[i]??'')+'\n').join('')+`\x1b[${height}A`);
|
|
50
|
+
if(performance.now()-start>=duration)cleanup();
|
|
51
|
+
}catch(e){cleanup(e);}
|
|
52
|
+
};
|
|
53
|
+
tick();if(!ended)timer=setInterval(tick,1000/6);
|
|
54
|
+
await done;
|
|
55
|
+
}catch(e){cleanup(e);await done;}
|
|
56
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 480"><title>Roady — the way forward</title>
|
|
2
|
+
<g id="far-wing">
|
|
3
|
+
<path id="far-sail" d="M 391 282 Q 439 202 524 154 L 500 243 L 467 229 L 443 290 L 418 278 L 399 322 Z" fill="#b7572c"/>
|
|
4
|
+
<path id="far-ribs" d="M 391 282 Q 439 202 524 154 M 391 282 L 500 243 M 391 282 L 443 290" fill="none" stroke="#3b9b9b" stroke-width="7"/>
|
|
5
|
+
</g>
|
|
6
|
+
<g id="tail">
|
|
7
|
+
<path id="long-tail" d="M 300 352 C 234 356 217 418 156 416 C 105 415 99 376 129 346 Q 89 356 88 395 C 86 443 154 457 201 434 Q 263 398 319 396 Z" fill="#217582"/>
|
|
8
|
+
<path id="tail-highlight" d="M 296 376 Q 231 393 192 426 Q 133 455 103 410 Q 136 436 184 416 Q 236 380 296 376 Z" fill="#63afa5"/>
|
|
9
|
+
<path id="compass-tip" d="M 128 322 L 148 355 L 126 349 L 108 365 Z" fill="#ffb957"/>
|
|
10
|
+
</g>
|
|
11
|
+
<g id="body">
|
|
12
|
+
<path id="body-mass" d="M 310 234 Q 357 217 395 255 L 409 331 Q 405 381 363 399 L 293 389 Q 266 349 282 303 Z" fill="#236f7c"/>
|
|
13
|
+
<path id="belly" d="M 355 255 Q 383 248 393 279 L 397 334 Q 387 373 360 383 L 335 366 Q 359 314 345 279 Z" fill="#ead8aa"/>
|
|
14
|
+
<path id="belly-lines" d="M 352 295 Q 372 307 396 292 M 350 324 Q 373 338 394 323 M 343 352 Q 366 363 383 352" fill="none" stroke="#b9976c" stroke-width="4"/>
|
|
15
|
+
<path id="rear-leg" d="M 304 346 Q 269 353 270 389 L 250 427 L 293 435 L 324 382 Z" fill="#328b95"/>
|
|
16
|
+
<path id="forward-leg" d="M 364 360 Q 389 362 390 391 L 427 417 L 459 419 L 469 435 L 407 437 L 357 403 Z" fill="#3a9ba0"/>
|
|
17
|
+
<path id="claws" d="M 255 424 L 247 439 L 262 434 L 272 424 L 269 440 L 288 434 M 441 422 L 436 438 L 449 433 L 457 425 L 456 439 L 473 434" fill="#fff0d0"/>
|
|
18
|
+
</g>
|
|
19
|
+
<g id="near-wing">
|
|
20
|
+
<path id="near-sail" d="M 318 267 Q 259 179 161 148 Q 190 193 161 239 L 204 232 L 227 287 L 262 271 L 298 321 Z" fill="#ec7938"/>
|
|
21
|
+
<path id="near-web" d="M 318 267 L 161 148 Q 239 208 227 287 L 262 271 L 298 321 Z" fill="#ffa24a"/>
|
|
22
|
+
<path id="near-ribs" d="M 318 267 Q 259 179 161 148 M 318 267 L 161 239 M 318 267 L 227 287" fill="none" stroke="#277d8a" stroke-width="8"/>
|
|
23
|
+
<path id="shoulder" d="M 294 279 C 294 265.745166 302.50658975 255 313 255 C 323.49341025 255 332 265.745166 332 279 C 332 292.254834 323.49341025 303 313 303 C 302.50658975 303 294 292.254834 294 279 Z" fill="#3a96a0"/>
|
|
24
|
+
</g>
|
|
25
|
+
<g id="neck">
|
|
26
|
+
<path id="neck-mass" d="M 331 176 L 386 185 Q 372 231 397 270 L 364 298 L 323 268 Q 344 224 331 176 Z" fill="#328f9a"/>
|
|
27
|
+
</g>
|
|
28
|
+
<g id="scarf">
|
|
29
|
+
<path id="streamer" d="M 346 244 Q 289 227 252 247 L 217 229 L 232 265 L 211 282 Q 279 286 351 260 Z" fill="#ed7233"/>
|
|
30
|
+
<path id="collar" d="M 320 229 Q 354 247 389 224 L 394 249 Q 357 272 321 250 Z" fill="#ff9c43"/>
|
|
31
|
+
</g>
|
|
32
|
+
<g id="satchel">
|
|
33
|
+
<path id="strap" d="M 324 267 L 335 265 L 376 350 L 366 355 Z" fill="#e1b56b"/>
|
|
34
|
+
<path id="bag" d="M 285 322 L 328 331 L 328 374 L 284 365 Z" fill="#b57743"/>
|
|
35
|
+
<path id="flap" d="M 285 322 L 329 331 L 313 350 L 283 338 Z" fill="#e2af6b"/>
|
|
36
|
+
</g>
|
|
37
|
+
<g id="head">
|
|
38
|
+
<path id="far-horn" d="M 344 142 Q 291 117 272 66 Q 313 102 360 113 Z" fill="#e5d9bc"/>
|
|
39
|
+
<path id="near-horn" d="M 382 130 Q 345 91 348 53 Q 369 90 409 105 L 405 138 Z" fill="#fff0d0"/>
|
|
40
|
+
<path id="ear" d="M 328 155 L 291 131 L 300 174 L 331 187 Z" fill="#4ea9a6"/>
|
|
41
|
+
<path id="face" d="M 331 133 Q 373 108 405 139 Q 426 166 406 203 L 370 223 L 331 207 Q 314 172 331 133 Z" fill="#3c9aa5"/>
|
|
42
|
+
<path id="muzzle" d="M 381 178 Q 426 164 460 183 L 466 204 Q 444 226 398 222 L 371 204 Z" fill="#6bb9b2"/>
|
|
43
|
+
<path id="jaw" d="M 387 214 Q 433 224 458 207 Q 450 238 410 232 Z" fill="#dfd6ae"/>
|
|
44
|
+
<path id="smile" d="M 386 204 Q 424 220 458 201" fill="none" stroke="#080d19" stroke-width="6"/>
|
|
45
|
+
<path id="nostril" d="M 442 182 Q 456 182 451 192 L 441 190 Z" fill="#080d19"/>
|
|
46
|
+
</g>
|
|
47
|
+
<g id="map">
|
|
48
|
+
<path id="scroll" d="M 360 332 L 446 300 L 460 328 L 373 362 Z" fill="#eddcb0"/>
|
|
49
|
+
<path id="scroll-edge" d="M 360 332 L 373 362 M 446 300 L 460 328" fill="none" stroke="#fff3ce" stroke-width="10"/>
|
|
50
|
+
<path id="map-tie" d="M 402 316 L 415 346" fill="none" stroke="#da6c37" stroke-width="8"/>
|
|
51
|
+
<path id="scroll-end" d="M 443 314 C 443 305.71572875 447.4771525 299 453 299 C 458.5228475 299 463 305.71572875 463 314 C 463 322.28427125 458.5228475 329 453 329 C 447.4771525 329 443 322.28427125 443 314 Z" fill="#fff0cb"/>
|
|
52
|
+
<path id="scroll-hole" d="M 449 314 C 449 310.13400675 450.790861 307 453 307 C 455.209139 307 457 310.13400675 457 314 C 457 317.86599325 455.209139 321 453 321 C 450.790861 321 449 317.86599325 449 314 Z" fill="#080d19"/>
|
|
53
|
+
</g>
|
|
54
|
+
<g id="arms">
|
|
55
|
+
<path id="map-arm" d="M 350 278 Q 370 274 375 297 L 403 311 L 419 301 L 433 313 Q 417 341 395 336 L 357 317 Z" fill="#439eaa"/>
|
|
56
|
+
<path id="point-arm" d="M 391 259 Q 402 247 419 263 L 442 276 L 487 250 L 493 234 L 520 223 L 526 232 L 508 245 L 526 245 L 526 255 L 499 264 L 454 305 Q 439 312 423 298 Z" fill="#59acb1"/>
|
|
57
|
+
<path id="grip-claws" d="M 407 305 L 414 297 L 418 313 M 418 309 L 427 302 L 430 317" fill="#fff0cf"/>
|
|
58
|
+
</g>
|
|
59
|
+
<g id="eyes">
|
|
60
|
+
<path id="far-eye-socket" d="M 324 167 C 324 153.19288125 332.50658975 142 343 142 C 353.49341025 142 362 153.19288125 362 167 C 362 180.80711875 353.49341025 192 343 192 C 332.50658975 192 324 180.80711875 324 167 Z" fill="#080d19"/>
|
|
61
|
+
<path id="far-eye-white" d="M 331 167 C 331 157.0588745 336.372583 149 343 149 C 349.627417 149 355 157.0588745 355 167 C 355 176.9411255 349.627417 185 343 185 C 336.372583 185 331 176.9411255 331 167 Z" fill="#ffffff"/>
|
|
62
|
+
<path id="far-pupil" d="M 342 167 C 342 163.13400675 344.23857625 160 347 160 C 349.76142375 160 352 163.13400675 352 167 C 352 170.86599325 349.76142375 174 347 174 C 344.23857625 174 342 170.86599325 342 167 Z" fill="#080d19"/>
|
|
63
|
+
<path id="near-eye-socket" d="M 363 163 C 363 146.98374225 373.745166 134 387 134 C 400.254834 134 411 146.98374225 411 163 C 411 179.01625775 400.254834 192 387 192 C 373.745166 192 363 179.01625775 363 163 Z" fill="#080d19"/>
|
|
64
|
+
<path id="near-eye-white" d="M 370 163 C 370 150.8497355 377.61115925 141 387 141 C 396.38884075 141 404 150.8497355 404 163 C 404 175.1502645 396.38884075 185 387 185 C 377.61115925 185 370 175.1502645 370 163 Z" fill="#ffffff"/>
|
|
65
|
+
<path id="near-pupil" d="M 386 163 C 386 159.13400675 388.23857625 156 391 156 C 393.76142375 156 396 159.13400675 396 163 C 396 166.86599325 393.76142375 170 391 170 C 388.23857625 170 386 166.86599325 386 163 Z" fill="#080d19"/>
|
|
66
|
+
</g>
|
|
67
|
+
</svg>
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import {renderSvgRig} from '../../../factory/svg-rig-contour.mjs';
|
|
2
|
+
export const STORY_DURATION=20000;
|
|
3
|
+
export const ROADY_CLIPS=Object.freeze([{id:'idle',title:'Ready for the next step',duration:8000},{id:'journey',title:'Chart · Blocked · Reroute · Arrive',duration:STORY_DURATION}]);
|
|
4
|
+
const clamp=x=>Math.max(0,Math.min(1,x));
|
|
5
|
+
const ease=x=>{x=clamp(x);return x*x*(3-2*x);};
|
|
6
|
+
const rot=(x,y,cx,cy,a)=>[cx+(x-cx)*Math.cos(a)-(y-cy)*Math.sin(a),cy+(x-cx)*Math.sin(a)+(y-cy)*Math.cos(a)];
|
|
7
|
+
export function poseRig(rig,scene,ms){
|
|
8
|
+
if(scene==='journey'&&ms>=STORY_DURATION)return poseRig(rig,'idle',ms-STORY_DURATION);
|
|
9
|
+
const t=ms,active=scene==='journey',env=active?ease(t/1400)*(1-ease((t-17500)/2500)):0;
|
|
10
|
+
const open=active?ease((t-1300)/2000)*(1-ease((t-17000)/2300)):0;
|
|
11
|
+
const blocked=active?ease((t-6300)/600)*(1-ease((t-10500)/1000)):0;
|
|
12
|
+
const success=active?ease((t-13500)/1100)*(1-ease((t-17000)/1800)):0;
|
|
13
|
+
const quiet=Math.sin(t/8000*Math.PI*2);
|
|
14
|
+
const headAngle=active?(.09*blocked-.08*success+.025*Math.sin(t/700)*env):.018*quiet;
|
|
15
|
+
const head=(x,y)=>rot(x,y,355,235,headAngle);
|
|
16
|
+
const paths=rig.paths.map(p=>({...p,polygons:p.polygons.map(poly=>poly.map(([x,y])=>{
|
|
17
|
+
if(p.group==='head'||p.group==='eyes'){
|
|
18
|
+
const q=head(x,y);if(p.id.endsWith('pupil'))q[0]+=active?(-5*success+2*blocked):1.5*quiet;return q;
|
|
19
|
+
}
|
|
20
|
+
if(p.group==='neck'){const w=clamp((285-y)/100),q=head(x,y);return [x+(q[0]-x)*w,y+(q[1]-y)*w];}
|
|
21
|
+
if(p.group==='near-wing')return rot(x,y,318,267,.035*quiet+.14*success);
|
|
22
|
+
if(p.group==='far-wing')return rot(x,y,391,282,-.025*quiet-.11*success);
|
|
23
|
+
if(p.group==='tail'){const w=clamp((300-x)/210);return [x+8*w*quiet,y-12*w*quiet-10*w*success];}
|
|
24
|
+
if(p.id==='streamer'){const w=clamp((346-x)/135);return [x,y+w*(4*quiet+5*env*Math.sin(t/450))];}
|
|
25
|
+
if(p.id==='point-arm'){const w=clamp((x-405)/95);return [x+5*w*env,y+14*w*blocked-10*w*success];}
|
|
26
|
+
return [x,y];
|
|
27
|
+
}))}));
|
|
28
|
+
const add=(id,rgb,poly,stroke=0,erase=false)=>paths.push({id,group:'roadmap',rgb,polygons:[poly],stroke,erase});
|
|
29
|
+
const line=(id,rgb,a,b,w=5)=>add(id,rgb,[a,b],w);
|
|
30
|
+
if(open>.001){
|
|
31
|
+
const X=u=>453+u*open,Y=(u,v)=>320+u*.18+v;
|
|
32
|
+
const pt=(u,v)=>[X(u),Y(u,v)];
|
|
33
|
+
add('map-shadow',[8,13,25],[pt(0,-4),pt(256,-4),pt(256,111),pt(0,111)],0,true);
|
|
34
|
+
add('unfurled-map',[229,213,164],[pt(0,0),pt(250,0),pt(250,104),pt(0,104)]);
|
|
35
|
+
line('map-edge',[255,242,205],pt(250,0),pt(250,104),7);
|
|
36
|
+
const route=[[15,75],[75,51],[140,67],[222,26]];
|
|
37
|
+
const draw=(id,points,progress,rgb,w)=>{
|
|
38
|
+
let count=clamp(progress)*(points.length-1);
|
|
39
|
+
for(let i=0;i<points.length-1;i++){
|
|
40
|
+
const f=clamp(count-i);if(f>0)line(id+i,rgb,pt(...points[i]),pt(points[i][0]+(points[i+1][0]-points[i][0])*f,points[i][1]+(points[i+1][1]-points[i][1])*f),w);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
draw('planned-route',route,(t-2800)/2700,[50,105,117],6);
|
|
44
|
+
for(let i=0;i<route.length;i++)if(t>3200+i*700){const [u,v]=route[i];add('milestone'+i,[37,104,118],[pt(u-6,v),pt(u,v-7),pt(u+6,v),pt(u,v+7)]);}
|
|
45
|
+
if(blocked>.01){const u=140,v=67;line('blocked-a',[199,58,40],pt(u-12*blocked,v-12*blocked),pt(u+12*blocked,v+12*blocked),8);line('blocked-b',[199,58,40],pt(u-12*blocked,v+12*blocked),pt(u+12*blocked,v-12*blocked),8);}
|
|
46
|
+
const detour=[[75,51],[114,20],[168,13],[222,26]];
|
|
47
|
+
if(t>9000)draw('detour',detour,(t-9000)/3500,[220,102,27],7);
|
|
48
|
+
if(t>12300){
|
|
49
|
+
const progress=clamp((t-12300)/2100)*(detour.length-1),i=Math.min(2,Math.floor(progress)),f=progress-i;
|
|
50
|
+
const [u,v]=[detour[i][0]+(detour[i+1][0]-detour[i][0])*f,detour[i][1]+(detour[i+1][1]-detour[i][1])*f];
|
|
51
|
+
add('traveller',[255,255,255],[pt(u-7,v),pt(u,v-8),pt(u+7,v),pt(u,v+8)]);
|
|
52
|
+
}
|
|
53
|
+
if(success>.001){
|
|
54
|
+
const h=48*success;line('flagpole',[37,90,103],pt(222,26),pt(222,26-h),5);
|
|
55
|
+
add('destination-flag',[238,104,35],[pt(222,26-h),pt(247,34-h+3*Math.sin(t/180)),pt(222,43-h)]);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const byId=new Map(paths.map(p=>[p.id,p]));
|
|
59
|
+
return {...rig,bounds:{left:65,top:35,right:730,bottom:510},paths,eyes:rig.eyes.map(e=>({...e,socket:byId.get(e.socket.id),white:byId.get(e.white.id),pupil:byId.get(e.pupil.id)}))};
|
|
60
|
+
}
|
|
61
|
+
export function renderRoady(rig,scene='idle',ms=0,options={}){
|
|
62
|
+
if(!ROADY_CLIPS.some(c=>c.id===scene))return {ok:false,code:'UNKNOWN_CLIP'};
|
|
63
|
+
if(!Number.isFinite(ms)||ms<0)return {ok:false,code:'INVALID_TIME'};
|
|
64
|
+
if(!options.still&&scene==='journey'&&ms>=STORY_DURATION)return renderRoady(rig,'idle',ms-STORY_DURATION,options);
|
|
65
|
+
const t=options.still?(scene==='journey'?15000:0):ms;
|
|
66
|
+
const caption=scene==='idle'?'Every goal needs a way forward.':t<2800?'First, let’s see where we’re going.':t<6300?'A destination. A few clear steps.':t<9000?'Ah. That route is blocked.':t<12300?'There’s another way around.':t<14500?'One step at a time.':t<17500?'Told you we’d find a way.':'Ready for the next adventure.';
|
|
67
|
+
return {...renderSvgRig(poseRig(rig,scene,t),options),scene,clip:scene,caption};
|
|
68
|
+
}
|
|
69
|
+
export const renderStory=(rig,ms,options={})=>renderRoady(rig,'journey',ms,options);
|