@h-rig/product-ad-website 0.0.6-alpha.283

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.
Files changed (38) hide show
  1. package/README.md +3 -0
  2. package/package.json +52 -0
  3. package/site/assets/favicon.svg +1 -0
  4. package/site/assets/og-card.png +0 -0
  5. package/site/assets/rig.css +199 -0
  6. package/site/assets/rig.js +66 -0
  7. package/site/assets/search-index.json +1 -0
  8. package/site/assets/site.css +46 -0
  9. package/site/assets/site.js +510 -0
  10. package/site/cli/index.html +88 -0
  11. package/site/config/index.html +256 -0
  12. package/site/docs/agent-tools/index.html +129 -0
  13. package/site/docs/architecture/index.html +42 -0
  14. package/site/docs/capabilities/index.html +84 -0
  15. package/site/docs/entities/index.html +68 -0
  16. package/site/docs/environment/index.html +62 -0
  17. package/site/docs/faq/index.html +17 -0
  18. package/site/docs/fleet-irc/index.html +44 -0
  19. package/site/docs/gate/index.html +128 -0
  20. package/site/docs/getting-started/index.html +50 -0
  21. package/site/docs/glossary/index.html +31 -0
  22. package/site/docs/index.html +68 -0
  23. package/site/docs/operator/index.html +49 -0
  24. package/site/docs/packages/index.html +36 -0
  25. package/site/docs/pi-extensions/index.html +15 -0
  26. package/site/docs/recipes/index.html +28 -0
  27. package/site/docs/runs/index.html +35 -0
  28. package/site/docs/security/index.html +29 -0
  29. package/site/docs/server/index.html +27 -0
  30. package/site/docs/swarm-commander/index.html +41 -0
  31. package/site/docs/task-sources/index.html +129 -0
  32. package/site/docs/troubleshooting/index.html +23 -0
  33. package/site/docs/validators/index.html +177 -0
  34. package/site/index.html +1653 -0
  35. package/site/plugins/examples/index.html +419 -0
  36. package/site/plugins/index.html +193 -0
  37. package/site/skills/index.html +140 -0
  38. package/site/variants/deck/index.html +1 -0
@@ -0,0 +1,1653 @@
1
+ <!doctype html><html lang="en"><head>
2
+ <meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
3
+ <title>Rig &mdash; air superiority as a binary</title><meta property="og:image" content="https://how.rig-does.work/assets/og-card.png"><meta property="og:title" content="Rig &mdash; air superiority as a binary"><meta name="twitter:card" content="summary_large_image">
4
+ <meta name="build" content="omp-v1"><meta name="description" content="Ground control for coding drones: launch the swarm from one terminal session, watch the fleet in the cnet, message any drone mid-flight, and land only SHA-pinned, review-cleared merges."><meta property="og:description" content="Ground control for coding drones: launch the swarm from one terminal session, watch the fleet in the cnet, message any drone mid-flight, and land only SHA-pinned, review-cleared merges.">
5
+ <link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
6
+ <style>
7
+ :root{
8
+ --bg:#070809;--bg-2:#0b0c0e;--panel:rgba(16,17,21,.72);--glass:rgba(20,22,27,.5);
9
+ --line:rgba(255,255,255,.07);--line-2:rgba(255,255,255,.13);
10
+ --ink:#f2f3f6;--ink-2:#aeb0ba;--ink-3:#6c6e79;--ink-4:#44464f;
11
+ --ac:#ccff4d;--ac-dim:#a9d63f;--ac-2:#56d8ff;
12
+ --mono:'JetBrains Mono',ui-monospace,monospace;--disp:'Space Grotesk',system-ui,sans-serif;
13
+ --gut:clamp(24px,5vw,90px);
14
+ }
15
+ *{box-sizing:border-box}html{scroll-behavior:smooth}
16
+ body{margin:0;background:var(--bg);color:var(--ink-2);font-family:var(--disp);font-size:17px;line-height:1.6;-webkit-font-smoothing:antialiased;overflow-x:hidden}
17
+ a{color:var(--ink);text-decoration:none;transition:color .18s}a:hover{color:var(--ac)}
18
+ code,pre,kbd{font-family:var(--mono)}::selection{background:var(--ac);color:#070809}
19
+ .bleed{width:100%;padding-left:var(--gut);padding-right:var(--gut)}
20
+
21
+ /* per-section dedicated scene canvas — absolute INSIDE each section, not a global fixed bg */
22
+ .stage-wrap{position:relative;overflow:hidden}
23
+ .scene{position:absolute;inset:0;width:100%;height:100%;z-index:0;display:block;pointer-events:none}
24
+ .scene-veil{position:absolute;inset:0;z-index:1;pointer-events:none}
25
+
26
+ .nav{position:fixed;top:0;left:0;right:0;z-index:40;backdrop-filter:blur(14px);background:linear-gradient(180deg,rgba(7,8,9,.72),rgba(7,8,9,0))}
27
+ .nav-in{display:flex;align-items:center;gap:34px;padding:22px var(--gut)}
28
+ .brand{font-family:var(--disp);font-weight:600;font-size:16px;color:var(--ink);display:flex;align-items:center;gap:9px}
29
+ .brand .mark{width:9px;height:9px;background:var(--ac);border-radius:2px;box-shadow:0 0 12px var(--ac);animation:pulse 2.4s ease-in-out infinite}
30
+ @keyframes pulse{50%{opacity:.45;transform:scale(.82)}}
31
+ .nav-links{display:flex;gap:26px;margin-left:auto;align-items:center}
32
+ .nav-links a{font-size:13.5px;color:var(--ink-3);position:relative}.nav-links a:hover{color:var(--ink)}
33
+ .nav-links a.active{color:var(--ink)}.nav-links a.active::after{content:"";position:absolute;left:0;right:0;bottom:-7px;height:1px;background:var(--ac)}
34
+ .nav-links a.ext{color:var(--ink-4)}
35
+
36
+ /* HERO */
37
+ .hero{position:relative;z-index:2;min-height:100vh;display:flex;flex-direction:column;justify-content:center;padding:120px var(--gut) 60px}
38
+ .hero .scene{z-index:0}
39
+ .hero>*:not(.scene):not(.scene-veil){position:relative;z-index:2}
40
+ .kick{font-family:var(--mono);font-size:13px;letter-spacing:.18em;text-transform:uppercase;color:var(--ink-3);display:flex;align-items:center;gap:14px;margin-bottom:26px}
41
+ .kick::before{content:"";width:44px;height:1px;background:var(--ac)}
42
+ .hl{font-family:var(--disp);font-weight:700;font-size:clamp(60px,13vw,220px);line-height:.86;letter-spacing:-.05em;color:var(--ink);margin:0;max-width:none}
43
+ .hl .ac{color:var(--ac)}
44
+ .herorow{display:flex;align-items:flex-end;justify-content:space-between;gap:48px;flex-wrap:wrap;margin-top:36px}
45
+ .lede{font-size:clamp(18px,1.6vw,22px);color:var(--ink-2);max-width:440px;line-height:1.5;margin:0}.lede b{color:var(--ink);font-weight:500}
46
+ .cta{display:flex;gap:14px;flex-wrap:wrap;align-items:center}
47
+ .btn{font-size:14px;padding:14px 28px;border-radius:9px;border:1px solid var(--line-2);color:var(--ink);transition:.18s;display:inline-flex;gap:9px;align-items:center;cursor:pointer;background:rgba(8,9,11,.4);backdrop-filter:blur(6px)}
48
+ .btn:hover{border-color:var(--ink-3);transform:translateY(-1px)}
49
+ .btn.pri{background:var(--ac);color:#070809;border-color:var(--ac);font-weight:600}
50
+ .btn.pri:hover{box-shadow:0 8px 36px rgba(204,255,77,.3);transform:translateY(-2px)}
51
+
52
+ /* cinematic terminal */
53
+ .cine{position:relative;z-index:2;margin:60px 0 0;border:1px solid var(--line-2);border-radius:16px;overflow:hidden;background:var(--panel);backdrop-filter:blur(20px);box-shadow:0 60px 160px -40px rgba(0,0,0,.85),0 0 0 1px rgba(255,255,255,.02) inset;width:100%}
54
+ .cine .cb{display:flex;align-items:center;gap:12px;padding:15px 20px;border-bottom:1px solid var(--line)}
55
+ .cine .cb .d{width:11px;height:11px;border-radius:50%;background:#2a2c33}
56
+ .cine .cb .d.a{background:#ff5f57}.cine .cb .d.b{background:#febc2e}.cine .cb .d.c{background:#28c840}
57
+ .cine .cb .ttl{margin-left:10px;font-family:var(--mono);font-size:12.5px;color:var(--ink-3)}
58
+ .cine .cb .st{margin-left:auto;font-family:var(--mono);font-size:11px;color:var(--ac-dim);display:flex;align-items:center;gap:7px}
59
+ .cine .cb .st i{width:6px;height:6px;border-radius:50%;background:var(--ac);box-shadow:0 0 9px var(--ac);font-style:normal;animation:pulse 2s infinite}
60
+ .cine .scr{padding:26px 30px;font-family:var(--mono);font-size:14px;line-height:1.85;min-height:420px;color:var(--ink-2);white-space:pre-wrap;word-break:break-word}
61
+ .cine .scr .pr{color:var(--ac)}.cine .scr .cm{color:var(--ink-4)}.cine .scr .ok{color:var(--ac)}
62
+ .cine .scr .c2{color:var(--ac-2)}.cine .scr .mg{color:#ff79b0}.cine .scr .dim{color:var(--ink-4)}.cine .scr .w{color:var(--ink)}
63
+ .cur{display:inline-block;width:9px;height:17px;background:var(--ac);vertical-align:text-bottom;margin-left:2px;animation:cb 1.05s steps(1) infinite;box-shadow:0 0 8px rgba(204,255,77,.6)}
64
+ @keyframes cb{50%{opacity:0}}
65
+ .scrollcue{margin-top:40px;font-family:var(--mono);font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:var(--ink-4);display:flex;align-items:center;gap:12px}
66
+ .scrollcue .ln{width:1px;height:34px;background:linear-gradient(var(--ac),transparent);animation:cue 2s ease-in-out infinite}
67
+ @keyframes cue{0%,100%{opacity:.3;transform:scaleY(.6)}50%{opacity:1;transform:scaleY(1)}}
68
+
69
+ /* hero install one-liner */
70
+ .inst{display:flex;align-items:center;gap:12px;margin-top:22px;padding:13px 18px;border:1px solid var(--line-2);border-radius:9px;background:rgba(8,9,11,.55);backdrop-filter:blur(8px);font-family:var(--mono);font-size:13.5px;max-width:640px}
71
+ .inst .pr{color:var(--ac)}
72
+ .inst code{color:var(--ink-2);overflow-x:auto;white-space:nowrap;flex:1}
73
+ .inst .cp{margin-left:auto;border:none;background:transparent;color:var(--ink-4);font-family:var(--mono);font-size:11px;cursor:pointer;letter-spacing:.06em;transition:.15s;flex:none}
74
+ .inst .cp:hover{color:var(--ac)}
75
+
76
+ /* hero: Ghostty-style — centered, the live terminal is the centerpiece */
77
+ .herocenter{text-align:center}
78
+ .herocenter .kick{justify-content:center;margin-bottom:22px}
79
+ .herocenter .hl{text-align:center;font-size:clamp(46px,8.6vw,132px);line-height:.9;margin:0 auto}
80
+ .herocenter .cine{max-width:860px;margin:42px auto 0}
81
+ /* big two-line closing statement — crisp DOM at rest, canvas shred only during the burst */
82
+ .hsub{line-height:1.06;color:var(--ink);margin:0 auto;max-width:1100px;letter-spacing:-.03em}
83
+ .hsub-a,.hsub-b .l2{font-size:clamp(28px,4vw,54px);font-weight:700;line-height:1.06}
84
+ .hsub-a{display:inline-block;color:var(--ink);margin-bottom:10px}
85
+ .hsub-b{position:relative;display:inline-block}
86
+ .hsub-b .l2{display:inline-block}
87
+ .hsub-b .l2p{color:var(--ink)}
88
+ .hsub-b .l2h{color:var(--ac);text-shadow:0 0 24px rgba(204,255,77,.4)}
89
+ .hsub-b .ripcv{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);opacity:0;pointer-events:none}
90
+ @media(prefers-reduced-motion:reduce){.hsub-b .ripcv{display:none}}
91
+ .herocenter .cta{justify-content:center;margin-top:26px}
92
+ .herocenter .inst{margin:20px auto 0}
93
+ .herocenter .scrollcue{justify-content:center;margin-top:46px}
94
+
95
+ /* operator's three moves band */
96
+ .moves{position:relative;z-index:2;padding:0;border-top:1px solid var(--line)}
97
+ .moves .mv-in{max-width:var(--maxw);margin:0 auto;display:grid;grid-template-columns:repeat(3,1fr)}
98
+ .mv{padding:64px var(--gut);border-right:1px solid var(--line)}
99
+ .mv:last-child{border-right:none}
100
+ .mv .v{font-family:var(--disp);font-size:clamp(30px,3.6vw,52px);font-weight:700;letter-spacing:-.035em;color:var(--ink);line-height:1}
101
+ .mv .v .ac{color:var(--ac)}
102
+ .mv p{color:var(--ink-3);font-size:15.5px;margin:18px 0 0;line-height:1.55;max-width:300px}
103
+ .mv p b{color:var(--ink-2)}
104
+ a.mv{display:block;color:inherit;transition:background .25s}
105
+ a.mv:hover{background:rgba(204,255,77,.035)}
106
+ .mv .go{display:inline-block;margin-top:22px;font-family:var(--mono);font-size:11.5px;letter-spacing:.12em;text-transform:uppercase;color:var(--ink-4);transition:color .2s,transform .2s}
107
+ a.mv:hover .go{color:var(--ac);transform:translateX(4px)}
108
+ @media(max-width:820px){.moves .mv-in{grid-template-columns:1fr}.mv{border-right:none;border-bottom:1px solid var(--line)}}
109
+
110
+ /* title chromatic-aberration glitch — subtle, periodic */
111
+ @keyframes glitch{0%,92%,100%{text-shadow:0 0 26px rgba(204,255,77,.28)}93%{text-shadow:-2px 0 rgba(86,216,255,.85),2px 0 rgba(255,121,176,.75),0 0 26px rgba(204,255,77,.28)}95%{text-shadow:2px 0 rgba(86,216,255,.85),-2px 0 rgba(255,121,176,.75),0 0 26px rgba(204,255,77,.28)}97%{text-shadow:0 0 26px rgba(204,255,77,.28)}}
112
+ .hl.gl .ac{animation:glitch 5.5s infinite}
113
+ @media(prefers-reduced-motion:reduce){.hl.gl .ac{animation:none}}
114
+
115
+ /* WHAT-IS clarity band (its own scene) */
116
+ .whatis{position:relative;z-index:2;padding:130px 0;border-top:1px solid var(--line)}
117
+ .whatis>*:not(.scene):not(.scene-veil){position:relative;z-index:3}
118
+ .whatis .wrapx{max-width:var(--maxw);margin:0 auto;padding:0 var(--gut)}
119
+ .whatis .eyebrow{margin-bottom:20px}
120
+ .whatis .big{font-family:var(--disp);font-size:clamp(27px,3.9vw,50px);font-weight:600;letter-spacing:-.03em;color:var(--ink);line-height:1.13;max-width:1120px;margin:0}
121
+ .whatis .big .ac{color:var(--ac)}.whatis .big .dim{color:var(--ink-3)}
122
+ .vcards{display:grid;grid-template-columns:repeat(3,1fr);gap:18px;margin-top:56px}
123
+ @media(max-width:900px){.vcards{grid-template-columns:1fr}}
124
+ .vcard{border:1px solid var(--line-2);border-radius:14px;padding:30px 28px;background:var(--glass);backdrop-filter:blur(10px);transition:.2s;position:relative;overflow:hidden}
125
+ .vcard::after{content:"";position:absolute;left:0;top:0;bottom:0;width:2px;background:var(--ac);transform:scaleY(0);transform-origin:top;transition:transform .3s}
126
+ .vcard:hover{border-color:var(--ink-3);transform:translateY(-3px)}
127
+ .vcard:hover::after{transform:scaleY(1)}
128
+ .vcard .vk{font-family:var(--mono);font-size:11px;letter-spacing:.16em;text-transform:uppercase;color:var(--ac-dim);margin-bottom:14px}
129
+ .vcard h3{font-family:var(--disp);font-size:22px;font-weight:600;color:var(--ink);margin:0 0 12px;letter-spacing:-.01em}
130
+ .vcard p{font-size:15px;color:var(--ink-3);line-height:1.6;margin:0}.vcard p b{color:var(--ink-2)}
131
+ .vcard ul{margin:18px 0 0;padding-left:0;color:var(--ink-2);font-size:14.5px;list-style:none}
132
+ .vcard li{margin:11px 0;position:relative;padding-left:18px}.vcard li b{color:var(--ink)}
133
+ .vcard li::before{content:"";position:absolute;left:0;top:8px;width:5px;height:5px;border-radius:50%;background:var(--ac);box-shadow:0 0 7px rgba(204,255,77,.5)}
134
+
135
+ /* staged terminal lines — reveal in sequence when the .rv container lands
136
+ (inline-block: the .scr's pre-wrap source newlines provide the breaks) */
137
+ .cine .scr .tl{display:inline-block;opacity:0;transform:translateX(-8px);transition:opacity .45s cubic-bezier(.2,.7,.2,1),transform .45s cubic-bezier(.2,.7,.2,1)}
138
+ .rv.in .tl{opacity:1;transform:none}
139
+
140
+ /* live spinner glyph (JS cycles the braille frame) */
141
+ .spin{color:var(--ac);display:inline-block;width:1ch}
142
+
143
+ /* dogfood band */
144
+ .dog{text-align:center;padding:120px var(--gut)}
145
+ .dog>*:not(.scene){position:relative;z-index:3}
146
+ .dog h2{font-family:var(--disp);font-size:clamp(34px,6vw,84px);font-weight:700;letter-spacing:-.04em;color:var(--ink);line-height:1;margin:0}
147
+ .dog h2 .ac{color:var(--ac)}
148
+ .dog p{color:var(--ink-3);font-size:18px;max-width:660px;margin:26px auto 0;line-height:1.6}
149
+ .dog p b{color:var(--ink-2)}
150
+
151
+ .rv{opacity:0;transform:translateY(30px);transition:opacity .9s cubic-bezier(.2,.7,.2,1),transform .9s cubic-bezier(.2,.7,.2,1)}.rv.in{opacity:1;transform:none}
152
+
153
+ /* sections full-bleed, each is a stage-wrap with its own scene */
154
+ section{position:relative;z-index:2;padding:150px 0;border-top:1px solid var(--line)}
155
+ section>.shead,section>.split,section>.pipeline,section>.pkgs{position:relative;z-index:3}
156
+ .shead{max-width:780px;margin-bottom:74px;padding:0 var(--gut)}
157
+ .eyebrow{font-family:var(--mono);font-size:12px;letter-spacing:.16em;text-transform:uppercase;color:var(--ac-dim);margin-bottom:18px}
158
+ .shead h2{font-family:var(--disp);font-size:clamp(36px,5.5vw,72px);font-weight:600;letter-spacing:-.035em;color:var(--ink);margin:0;line-height:1}
159
+ .shead p{color:var(--ink-3);font-size:18px;margin:22px 0 0;line-height:1.55;max-width:600px}
160
+
161
+ .pipeline{display:grid;grid-template-columns:repeat(5,1fr);border-top:1px solid var(--line);border-bottom:1px solid var(--line);background:rgba(7,8,9,.35);backdrop-filter:blur(3px)}
162
+ .stage{padding:44px var(--gut);border-right:1px solid var(--line);position:relative;transition:background .3s}
163
+ .stage:nth-child(5n){border-right:none}
164
+ .stage.on{background:rgba(204,255,77,.05)}
165
+ .stage .num{font-family:var(--mono);font-size:13px;color:var(--ink-4)}.stage.on .num{color:var(--ac)}
166
+ .stage h4{font-family:var(--disp);font-size:21px;font-weight:600;color:var(--ink);margin:16px 0 10px;letter-spacing:-.01em}
167
+ .stage p{font-size:14px;color:var(--ink-3);margin:0;line-height:1.5}
168
+ .stage .bar{position:absolute;left:0;right:0;bottom:-1px;height:2px;background:var(--ac);transform:scaleX(0);transform-origin:left;transition:transform .5s}.stage.on .bar{transform:scaleX(1)}
169
+ @media(max-width:820px){.pipeline{grid-template-columns:1fr 1fr}.stage:nth-child(5n){border-right:1px solid var(--line)}}
170
+
171
+ .split{display:grid;grid-template-columns:1fr 1fr;gap:80px;align-items:center;padding:0 var(--gut);margin:0}
172
+ .split.rev{direction:rtl}.split.rev>*{direction:ltr}
173
+ .split h3{font-family:var(--disp);font-size:clamp(28px,3.6vw,46px);font-weight:600;color:var(--ink);letter-spacing:-.03em;margin:0 0 22px;line-height:1.04}
174
+ .split p{color:var(--ink-3);font-size:17px;line-height:1.6;margin:0 0 16px}.split p b{color:var(--ink-2)}
175
+ .panel{background:var(--panel);border:1px solid var(--line-2);border-radius:14px;backdrop-filter:blur(14px);overflow:hidden}
176
+ .panel .pb{padding:14px 18px;border-bottom:1px solid var(--line);font-family:var(--mono);font-size:12px;color:var(--ink-3)}
177
+ .panel .pp{padding:24px;font-family:var(--mono);font-size:13px;line-height:1.95;white-space:pre-wrap;word-break:break-word}
178
+ .pp .ac{color:var(--ac)}.pp .c2{color:var(--ac-2)}.pp .dim{color:var(--ink-4)}.pp .mg{color:#ff79b0}.pp .ok{color:var(--ac)}
179
+
180
+ .pkgs{display:grid;grid-template-columns:repeat(3,1fr);gap:0;border-top:1px solid var(--line);background:rgba(7,8,9,.35);backdrop-filter:blur(3px)}
181
+ .pk{display:block;color:inherit;padding:38px var(--gut);border-right:1px solid var(--line);border-bottom:1px solid var(--line);transition:.2s}
182
+ .pk:nth-child(3n){border-right:none}
183
+ .pk:hover{background:var(--bg-2)}
184
+ .pk .nm{font-family:var(--mono);font-size:15px;color:var(--ink);margin-bottom:10px}.pk:hover .nm{color:var(--ac)}
185
+ .pk .ds{font-size:14px;color:var(--ink-3);line-height:1.5}
186
+ @media(max-width:820px){.pkgs{grid-template-columns:1fr}.pk{border-right:none}}
187
+
188
+ .ctaband{text-align:center;padding:190px var(--gut)}
189
+ .ctaband>*:not(.scene){position:relative;z-index:3}
190
+ .ctaband h2{font-family:var(--disp);font-size:clamp(44px,9vw,140px);font-weight:700;letter-spacing:-.045em;color:var(--ink);line-height:.9;margin:0}
191
+ .ctaband h2 .ac{color:var(--ac)}
192
+ .ctaband p{color:var(--ink-3);font-size:20px;max-width:540px;margin:30px auto 0}
193
+
194
+ footer{position:relative;z-index:2;border-top:1px solid var(--line);padding:80px var(--gut) 100px;background:var(--bg)}
195
+ .foot{display:flex;flex-wrap:wrap;gap:60px;justify-content:space-between}
196
+ .foot .col h5{font-family:var(--mono);font-size:11px;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-4);margin:0 0 16px;font-weight:500}
197
+ .foot .col a{display:block;color:var(--ink-3);font-size:14px;padding:5px 0}.foot .col a:hover{color:var(--ink)}
198
+ .foot .sig{font-family:var(--mono);font-size:12px;color:var(--ink-4);max-width:300px;line-height:1.7}.foot .sig b{color:var(--ink-2);font-weight:400}
199
+
200
+ @media(max-width:900px){.split,.split.rev{grid-template-columns:1fr;direction:ltr;gap:40px}.nav-links a.ext{display:none}.nav-links{gap:18px}.herorow{flex-direction:column;align-items:flex-start}}
201
+
202
+ /* ASCII drone mascot inside hero terminal */
203
+ .cine .scr.mascot{min-height:470px;padding:20px 8px;overflow:hidden;display:flex;align-items:center;justify-content:center}
204
+ .cine .scr.mascot .sprite{white-space:pre;text-align:left;display:inline-block;font-size:13px;line-height:1.08;letter-spacing:1px}
205
+ @media(min-width:1100px){.cine .scr.mascot .sprite{font-size:16px;line-height:1.12}}
206
+ @media(max-width:700px){.cine .scr.mascot .sprite{font-size:10px;letter-spacing:.5px}}
207
+ .g-d{color:#ccff4d;text-shadow:0 0 6px rgba(204,255,77,.35)}
208
+ .g-m{color:#a9d63f}
209
+ .g-c{color:#56d8ff}
210
+ .g-r{color:#5f6b3a}
211
+ .g-s{color:#3a4a1e}
212
+ .g-rb{color:#7d9e34;text-shadow:0 0 4px rgba(204,255,77,.22)}
213
+ .g-eye{color:#eaffb0;text-shadow:0 0 10px rgba(204,255,77,.9),0 0 18px rgba(204,255,77,.5)}
214
+ .g-b{color:transparent}
215
+
216
+ /* ---- content container: wide but readable (full-bleed dividers/scenes stay edge-to-edge) ---- */
217
+ :root{--maxw:1520px}
218
+ .hero{align-items:center}
219
+ .hero>.kick,.hero>.hl,.hero>.scrollcue{width:100%;max-width:var(--maxw);margin-left:auto;margin-right:auto}
220
+ .shead{max-width:var(--maxw);margin-left:auto;margin-right:auto}
221
+ .shead h2,.shead p,.shead .eyebrow{max-width:860px}
222
+ .split{max-width:var(--maxw);margin-left:auto;margin-right:auto}
223
+ .pipeline{max-width:var(--maxw);margin-left:auto;margin-right:auto}
224
+ .pkgs{max-width:var(--maxw);margin-left:auto;margin-right:auto}
225
+ .ctaband>.rv,.ctaband>.cta{max-width:980px;margin-left:auto;margin-right:auto}
226
+ footer .foot{max-width:var(--maxw);margin-left:auto;margin-right:auto}
227
+
228
+ /* ---- atmosphere: grain, scanlines, aurora, divider accents, hover physics ---- */
229
+ body::after{content:"";position:fixed;inset:0;z-index:60;pointer-events:none;opacity:.05;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)' opacity='.55'/%3E%3C/svg%3E")}
230
+ .hero::after{content:"";position:absolute;inset:0;z-index:1;pointer-events:none;background:radial-gradient(60% 42% at 50% 10%,rgba(204,255,77,.07),transparent 62%),radial-gradient(42% 34% at 16% 80%,rgba(86,216,255,.05),transparent 65%),radial-gradient(46% 36% at 86% 72%,rgba(204,255,77,.04),transparent 65%)}
231
+ .cine .scr{position:relative}
232
+ .cine .scr::after,.panel .pp::after{content:"";position:absolute;inset:0;pointer-events:none;background:repeating-linear-gradient(0deg,rgba(255,255,255,.016) 0 1px,transparent 1px 3px)}
233
+ .panel .pp{position:relative}
234
+ section::before,.moves::before{content:"";position:absolute;top:-1px;left:50%;transform:translateX(-50%);width:min(560px,62%);height:1px;background:linear-gradient(90deg,transparent,rgba(204,255,77,.38),transparent);z-index:4;pointer-events:none}
235
+ .herocenter .cine{box-shadow:0 60px 160px -40px rgba(0,0,0,.85),0 0 110px -28px rgba(204,255,77,.16),0 0 0 1px rgba(255,255,255,.02) inset}
236
+ .panel{transition:transform .25s,border-color .25s,box-shadow .25s}
237
+ .panel:hover{transform:translateY(-3px);border-color:rgba(204,255,77,.22);box-shadow:0 28px 90px -38px rgba(0,0,0,.85)}
238
+ .panel::before{content:"";position:absolute;left:8%;right:8%;top:0;height:1px;background:linear-gradient(90deg,transparent,rgba(204,255,77,.55),transparent);opacity:0;transition:opacity .3s;z-index:2}
239
+ .panel:hover::before{opacity:1}
240
+ .panel{position:relative}
241
+ .stage:hover{background:rgba(204,255,77,.025)}
242
+ .pk:hover{transform:translateY(-2px)}
243
+ @media(prefers-reduced-motion:reduce){.panel:hover,.pk:hover{transform:none}}
244
+ </style><link rel="stylesheet" href="assets/site.css?v=v9"><link rel="icon" type="image/svg+xml" href="assets/favicon.svg"></head><body><nav class="nav"><div class="nav-in">
245
+ <a class="brand" href="./"><span class="mark"></span>Rig</a>
246
+ <div class="nav-links">
247
+ <a href="./" class="active">overview</a><a href="docs/index.html">docs</a>
248
+ <a href="plugins/index.html">plugins</a><a href="skills/index.html">skills</a>
249
+ <a href="cli/index.html">cli</a><a href="config/index.html">config</a>
250
+ <a href="https://github.com/humanity-org/rig" class="ext">github</a>
251
+ </div></div></nav>
252
+
253
+ <header class="hero stage-wrap herocenter">
254
+ <canvas class="scene" data-scene="dispatch"></canvas>
255
+ <h1 class="hl gl">Air superiority, <span class="ac">as a binary.</span></h1>
256
+ <div class="cine"><div class="cb"><span class="d a"></span><span class="d b"></span><span class="d c"></span><span class="ttl">rig &mdash; OMP session cnet</span><span class="st"><i></i>live</span></div><div class="scr" id="cine"></div></div>
257
+ <div class="inst"><span class="pr">$</span><code id="instcmd">curl -fsSL https://where.rig-does.work/install | bash</code><button class="cp" onclick="navigator.clipboard&&navigator.clipboard.writeText(document.getElementById('instcmd').innerText);this.innerText='copied';var b=this;setTimeout(function(){b.innerText='copy'},1200)">copy</button></div>
258
+ <div class="scrollcue"><span class="ln"></span>scroll to descend</div>
259
+ </header>
260
+
261
+ <section class="moves">
262
+ <div class="mv-in">
263
+ <a class="mv rv" href="#loop"><div class="v">Arm.</div><p>Install once, type bare <code>rig</code> &mdash; ground control opens with the cnet live, the fleet linked, and every verb loaded.</p><span class="go">open the loop &rarr;</span></a>
264
+ <a class="mv rv" href="#swarmer"><div class="v">Command.</div><p>Say it in chat &mdash; the <b>Swarm Commander</b> reads the cnet, picks the verb, and moves the fleet. Receipts, or it didn&rsquo;t happen.</p><span class="go">meet the swarm commander &rarr;</span></a>
265
+ <a class="mv rv" href="#ops"><div class="v">Clear.</div><p>The cnet lives in your transcript &mdash; <code>/fleet</code>, <code>/tasks</code>, and <code>/drone</code> drop live cards inline; the one that needs you says so.</p><span class="go">the ops story &rarr;</span></a>
266
+ </div>
267
+ </section>
268
+
269
+ <section class="stage-wrap">
270
+ <canvas class="scene" data-scene="nightshift"></canvas>
271
+ <div class="shead rv"><div class="eyebrow">the night shift</div><h2>Yesterday&rsquo;s backlog. This morning&rsquo;s <span class="ac">merges.</span></h2><p>Ammunition comes from your real tracker &mdash; GitHub Issues, Linear, or your own source. A drone takes the task, flies the sortie in its own sealed session, fights the review, and lands a SHA-pinned merge. Close ground control &mdash; the sorties keep flying; reopen it and the fleet is right there in the cnet.</p></div>
272
+ </section>
273
+
274
+ <section id="loop" class="stage-wrap">
275
+ <canvas class="scene" data-scene="cnet"></canvas>
276
+ <div class="shead rv"><div class="eyebrow">the loop</div><h2>One terminal command. One live session.</h2><p>Install Rig, type <code>rig</code>, and run the war from the transcript. The cnet streams what the fleet is doing; every move you make comes back with a receipt.</p></div>
277
+ <div class="pipeline rv" id="pipe">
278
+ <div class="stage" data-i="0"><div class="num">01</div><h4>Install</h4><p>The one-line installer puts the self-contained <code>rig</code> binary on your PATH.</p><span class="bar"></span></div>
279
+ <div class="stage" data-i="1"><div class="num">02</div><h4>Open</h4><p>Type <code>rig</code>. The session opens with the fleet already wired.</p><span class="bar"></span></div>
280
+ <div class="stage" data-i="2"><div class="num">03</div><h4>Dispatch</h4><p>Dispatch from <code>/fleet</code> &mdash; or just tell the Commander what to launch.</p><span class="bar"></span></div>
281
+ <div class="stage" data-i="3"><div class="num">04</div><h4>Watch</h4><p>Fleet, active runs, and attention stream in as live cards.</p><span class="bar"></span></div>
282
+ <div class="stage" data-i="4"><div class="num">05</div><h4>Decide</h4><p>The card that needs you offers the verbs. One keystroke answers.</p><span class="bar"></span></div>
283
+ </div></section>
284
+
285
+ <section id="swarmer" class="stage-wrap">
286
+ <canvas class="scene" data-scene="swarmer"></canvas>
287
+ <div class="shead rv"><div class="eyebrow">the swarm commander</div><h2>Say the word. The swarm <span style="color:var(--ac)">moves.</span></h2><p>The Swarm Commander sits in your chat and speaks fleet. Ask for anything &mdash; it reads the cnet, picks the right verb, and executes with the receipt to prove it.</p></div>
288
+ <div class="split rv">
289
+ <div><h3>Every verb your build ships. Zero ceremony.</h3>
290
+ <p>Tasks, fleet state, dispatch, steering, stop, restart, model changes &mdash; plain chat. The Commander discovers whatever verbs your plugins contribute; a tailored <code>.rig/rig.config.ts</code> build hands it exactly your arsenal.</p>
291
+ <p>And it can&rsquo;t hand-wave. Every action binds to the exact item and revision it saw, and nothing counts as done until the durable receipt lands. A send is not a success &mdash; the receipt is.</p>
292
+ </div>
293
+ <div class="cine" style="margin:0"><div class="cb"><span class="d a"></span><span class="d b"></span><span class="d c"></span><span class="ttl">rig &mdash; Commander role</span><span class="st"><i></i>operator turn</span></div><div class="scr" style="min-height:0"><span class="tl" style="transition-delay:.05s"><span class="pr">you</span> <span class="w">show ready tasks and dispatch #412</span></span>
294
+ <span class="tl" style="transition-delay:.3s"><span class="c2">swarmer</span> reading the tracker through the cnet</span>
295
+ <span class="tl" style="transition-delay:.55s"><span class="dim">cnet</span> tasks &middot; 12 ready</span>
296
+ <span class="tl" style="transition-delay:.8s"><span class="c2">swarmer</span> dispatching task #412</span>
297
+ <span class="tl" style="transition-delay:1.05s"><span class="ok">receipt</span> applied &middot; task #412 &middot; run 0xA1</span>
298
+ <span class="tl" style="transition-delay:1.3s"><span class="pr">you</span> <span class="w">tell 0xA1 to rerun the focused spec</span></span>
299
+ <span class="tl" style="transition-delay:1.55s"><span class="c2">swarmer</span> steering run 0xA1</span>
300
+ <span class="tl" style="transition-delay:1.8s"><span class="dim">cnet</span> send direction &middot; run 0xA1</span>
301
+ <span class="tl" style="transition-delay:2.05s"><span class="ok">receipt</span> delivered &middot; on the record</span></div></div>
302
+ </div></section>
303
+
304
+ <section id="net" class="stage-wrap">
305
+ <canvas class="scene" data-scene="ircnet"></canvas>
306
+ <div class="shead rv"><div class="eyebrow">the fleet net</div><h2>Every callsign, one <span class="ac">net.</span></h2><p>Drones aren&rsquo;t fire-and-forget. Every airframe is on the net: DM a drone mid-flight and it answers in the cnet; a drone that hits something worth your time raises you first. Every message lands in the run&rsquo;s journal &mdash; part of the mission record, forever.</p></div>
307
+ <div class="split rv">
308
+ <div><h3>Message a parked drone. Watch it wake.</h3>
309
+ <p>The net is OMP&rsquo;s own IRC, extended across the fleet: operator, Commander, and drones are addressable peers &mdash; send, reply, inbox, roster. Nothing rides outside the record; every send appends to the owning run&rsquo;s journal.</p>
310
+ <p>Even sleep isn&rsquo;t off the net. Message a parked drone and it revives &mdash; same run id, same branch, your DM read first.</p></div>
311
+ <div class="panel"><div class="pb">run 0xA1 &middot; on the net</div><div class="pp"><span class="c2">drone</span> running &middot; implement
312
+ <span class="c2">message</span> to operator &middot; &ldquo;APP_TOKEN is missing&rdquo;
313
+ <span class="ok">receipt</span> journaled &middot; seq 121
314
+ <span class="ac">operator</span> &ldquo;use the staging token&rdquo; &rarr; 0xA1
315
+ <span class="ok">receipt</span> delivered &middot; read at the next step
316
+ <span class="dim">// DM a parked drone &rarr; same run wakes &middot; message read first</span></div></div>
317
+ </div></section>
318
+
319
+ <section class="stage-wrap">
320
+ <canvas class="scene" data-scene="plansplit"></canvas>
321
+ <div class="shead rv"><div class="eyebrow">the tasking</div><h2>The backlog is the <span class="ac">ammo belt.</span></h2><p>Work arrives however you already track it &mdash; GitHub Issues, Linear, or your own source. <code>/tasks</code> shows what&rsquo;s ready; a <code>/fleet</code> action or <code>rig run dispatch</code> sends a drone at it.</p></div>
322
+ <div class="split rv">
323
+ <div><h3>The tracker stays the boss.</h3>
324
+ <p>Rig never copies your backlog into a private database &mdash; it reads the source live and writes back through it. Dispatch a task, and when the merge lands the task closes where it lives.</p></div>
325
+ <div class="panel"><div class="pb">/tasks &middot; configured source</div><div class="pp"><span class="ok">ready</span> #421 auth: token refresh
326
+ <span class="ok">ready</span> #430 api: rate limiter
327
+ <span class="c2">running</span> #433 ui: usage panel
328
+ <span class="ac">actions</span> Create task &middot; Rename &middot; Close
329
+ <span class="dim">// read live from the source &middot; closed by the merge</span></div></div>
330
+ </div></section>
331
+
332
+ <section class="stage-wrap">
333
+ <canvas class="scene" data-scene="relay"></canvas>
334
+ <div class="shead rv"><div class="eyebrow">the walkthrough</div><h2>Install once. Type rig.</h2><p>The launcher drops you into the session. From there you talk, dispatch, watch the fleet, answer what needs you, and pull up any drone &mdash; without leaving the transcript.</p></div>
335
+ <div class="rv" style="position:relative;z-index:3;max-width:var(--maxw);margin:0 auto;padding:0 var(--gut)">
336
+ <div class="cine" style="margin:0"><div class="cb"><span class="d a"></span><span class="d b"></span><span class="d c"></span><span class="ttl">rig &mdash; task #418</span><span class="st"><i></i>one command</span></div><div class="scr" style="min-height:0"><span class="tl" style="transition-delay:.05s"><span class="pr">$</span> <span class="w">rig</span></span>
337
+ <span class="tl" style="transition-delay:.25s"><span class="c2">&rsaquo; open</span> OMP session &middot; workspace linked &middot; Rig extension <span class="ok">&#10003;</span></span>
338
+ <span class="tl" style="transition-delay:.45s"><span class="c2">&rsaquo; cnet</span> live &middot; the fleet in your transcript</span>
339
+ <span class="tl" style="transition-delay:.65s"><span class="c2">&rsaquo; commander</span> on station</span>
340
+ <span class="tl" style="transition-delay:.85s"><span class="c2">&rsaquo; tasks</span> <span class="w">/tasks</span> filter &middot; task #418 ready to dispatch</span>
341
+ <span class="tl" style="transition-delay:1.05s"><span class="ac">&rsaquo; dispatch</span> task #418 &rarr; run 0xA1 &middot; receipt in the cnet</span>
342
+ <span class="tl" style="transition-delay:1.25s"><span class="c2">&rsaquo; attention</span> the card that needs you renders inline</span>
343
+ <span class="tl" style="transition-delay:1.45s"><span class="c2">&rsaquo; cnet</span> <span class="w">/fleet</span> &middot; <span class="w">/drone 0xA1</span> inline cards</span>
344
+ <span class="tl" style="transition-delay:1.65s"><span class="ac">&rsaquo; gate</span> <span class="w">@rig/pr-review-plugin</span> strict merge gate armed</span>
345
+ <span class="tl" style="transition-delay:1.85s"><span class="c2">&rsaquo; attach</span> <span class="w">rig attach 0xA1</span> &middot; straight into the cockpit</span>
346
+ <span class="tl" style="transition-delay:2.05s"><span class="ok">&rsaquo; live</span> runId === sessionId &middot; OMP JSONL journal</span></div></div>
347
+ </div></section>
348
+
349
+ <section class="stage-wrap">
350
+ <canvas class="scene" data-scene="lanes"></canvas>
351
+ <div class="split rev rv">
352
+ <div><h3>One CNet. Drones placed anywhere.</h3>
353
+ <p>Your session owns the conversation; the cnet shows the fleet. Local and remote are just parking spots &mdash; every drone flies the same contract wherever it runs, and one keystroke opens any of them.</p></div>
354
+ <div class="panel"><div class="pb">Rig session &middot; live fleet</div><div class="pp"><span class="c2">0xA1</span> auth-fix run <span class="ok">local</span> <span class="dim">&middot; active</span>
355
+ <span class="c2">0xB7</span> collab refactor <span class="ac">remote</span> <span class="dim">&middot; live</span>
356
+ <span class="c2">0xC3</span> docs polish <span class="ok">local</span> <span class="dim">&middot; merged</span>
357
+ <span class="c2">0xD9</span> design review <span class="ac">remote</span> <span class="dim">&middot; needs attention</span>
358
+ <span class="dim">&hellip; +18 run sessions in the fleet</span>
359
+
360
+ <span class="dim">// OMP is authority &middot; relay is coordination</span>
361
+ <span class="ok">/fleet &rarr; /drone</span> <span class="dim">&middot; the cnet keeps the receipts</span></div></div>
362
+ </div></section>
363
+
364
+ <section class="stage-wrap">
365
+ <canvas class="scene" data-scene="hangar"></canvas>
366
+ <div class="shead rv"><div class="eyebrow">the harness</div><h2>Every drone gets its own hangar.</h2><p>A dispatched run launches from a sealed bay: its own git worktree, its own runtime <code>HOME</code> and credentials, an OS sandbox that fails closed, and a toolchain hardlinked from one binary. Sorties never share air.</p></div>
367
+ <div class="split rv">
368
+ <div><h3>Isolation is the airframe, not a checkbox.</h3>
369
+ <p>Two drones on the same repo can&rsquo;t touch each other&rsquo;s fuel: separate branches in separate worktrees, separate <code>HOME</code>, per-run identity with token keys stripped and auth through a state file. The OS sandbox is <b>enforce by default</b> &mdash; bwrap on Linux, seatbelt on macOS &mdash; and fails closed when the host can&rsquo;t confine, instead of silently flying unprotected.</p>
370
+ <p>Crash one drone: the swarm doesn&rsquo;t feel it. Close ground control: the sorties keep flying &mdash; every drone is its own process with the mission journal onboard.</p></div>
371
+ <div class="panel"><div class="pb">run 0xA1 &mdash; bay manifest</div><div class="pp"><span class="c2">worktree</span> .worktrees/0xA1 <span class="dim">&middot; own branch, own index</span>
372
+ <span class="c2">home</span> .worktrees/0xA1/.rig/home <span class="dim">&middot; isolated runtime state</span>
373
+ <span class="c2">identity</span> per-run credentials <span class="dim">&middot; token keys stripped</span>
374
+ <span class="c2">sandbox</span> <span class="w">enforce</span> &middot; seatbelt <span class="ok">&#10003;</span> <span class="dim">&middot; fails closed</span>
375
+ <span class="c2">toolchain</span> rig-run &middot; rig-agent &middot; rig-shell <span class="dim">&rarr; hardlinks of one binary</span>
376
+
377
+ <span class="dim">// nothing shared &middot; nothing leaked &middot; nothing borrowed</span></div></div>
378
+ </div></section>
379
+
380
+ <section class="stage-wrap">
381
+ <canvas class="scene" data-scene="scramble"></canvas>
382
+ <div class="shead rv"><div class="eyebrow">the scramble</div><h2>Wheels-up before npm finds its <span class="ac">lockfile.</span></h2><p>Provisioning is native code, not npm scripts. The bay materializes straight from the compiled binary &mdash; worktree, sealed identity, dependency layer, toolchain &mdash; zero package installs on the runway.</p></div>
383
+ <div class="split rv">
384
+ <div><h3>One binary, every role.</h3>
385
+ <p>No interpreter boots and no package tree gets copied. The same executable wakes up as <code>rig-git</code>, the sandboxed shell, or the tool gateway depending on the name it answers to &mdash; the whole fleet shares one build, one disk footprint, one attack surface.</p>
386
+ <div class="panel"><div class="pb">bay 07 &middot; provisioning</div><div class="pp"><span class="c2">worktree</span> run/0xA1 checked out <span class="ok">ready</span>
387
+ <span class="c2">identity</span> isolated HOME &middot; scoped credentials <span class="ok">ready</span>
388
+ <span class="c2">deps</span> runtime layer hardlinked <span class="ok">ready</span>
389
+ <span class="c2">toolchain</span> multicall Rig binary <span class="ok">ready</span>
390
+ <span class="ac">status</span> armed &middot; wheels-up</div></div>
391
+ </div></section>
392
+
393
+ <section class="stage-wrap">
394
+ <canvas class="scene" data-scene="carrier"></canvas>
395
+ <div class="shead rv"><div class="eyebrow">the extension</div><h2>One OMP session. Rig inside it.</h2><p>Rig launches Oh My Pi with the Rig extension aboard, pinned to an exact runtime version. The session opens with the fleet already loaded &mdash; no service to start, nothing to babysit.</p></div>
396
+ <div class="split rv">
397
+ <div><h3>Configured once. Loaded into every session.</h3>
398
+ <p>Bare <code>rig</code> carries your extension set and team packages from <code>runtime.pi.packages</code> into every session &mdash; yours and every drone&rsquo;s. Same versions, same tools, every time; the cnet operates them all.</p>
399
+ <a class="btn" href="docs/pi-extensions/index.html" style="margin-top:8px">OMP/Pi extensions</a></div>
400
+ <div class="panel"><div class="pb">same extension, every session</div><div class="pp"><span class="c2">omp/pi</span> <span class="w" data-pi-version>16.3.4</span> <span class="dim">bundled &middot; pinned</span>
401
+ <span class="c2">extensions</span> pi-subagents &middot; pi-web-access &middot; <span class="dim">your-ext</span>
402
+ <span class="dim">// runtime.pi.packages &rarr; OMP/Pi session packages</span>
403
+
404
+ <span class="ac">session</span> OMP + Rig &middot; cnet ready <span class="ok">&#10003;</span>
405
+ <span class="ac">drone 0xB7</span> remote placement &middot; supervised <span class="ok">&#10003;</span>
406
+ <span class="ac">attach 0xC3</span> terminal or web &middot; one keystroke <span class="ok">&#10003;</span>
407
+ <span class="dim">// same version + extension set, installed at session start</span></div></div>
408
+ </div></section>
409
+
410
+ <section id="cycle" class="stage-wrap">
411
+ <canvas class="scene" data-scene="strikeloop"></canvas>
412
+ <div class="shead rv"><div class="eyebrow">the recon-strike loop</div><h2>Reviews don&rsquo;t wait for you.</h2><p>Recon confirms the target; the drone flies the sortie; assessment sends it back up. CI fails &mdash; the drone fixes and re-pushes. The reviewer <b>executes the code</b>, reproduces the bug it suspects, and pins the finding to lines &mdash; the drone answers it in the diff. Push by push, until the evidence is clean on the current head.</p></div>
413
+ <div class="split rev rv">
414
+ <div><h3>Target confirmed. Sortie. Assess. Again.</h3>
415
+ <p>No human relays anything. Review findings and failing checks feed the drone directly, and the loop runs on its own &mdash; the same unattended cycle that Rig&rsquo;s own PRs fly through before they land.</p>
416
+ <p>Watched live on a real PR: finding &rarr; fix &rarr; deeper finding &rarr; the drone removed the root cause outright instead of patching around it &rarr; clean score &rarr; merged. Zero relayed instructions, receipts in the cnet.</p></div>
417
+ <div class="cine" style="margin:0"><div class="cb"><span class="d a"></span><span class="d b"></span><span class="d c"></span><span class="ttl">PR #418 &mdash; review loop</span><span class="st"><i></i>autonomous</span></div><div class="scr" style="min-height:0"><span class="tl" style="transition-delay:.05s"><span class="c2">recon</span> reviewer ran the code &middot; bug reproduced &middot; finding <span class="mg">P1</span> pinned to lines</span>
418
+ <span class="tl" style="transition-delay:.3s"><span class="ac">sortie 1</span> fix pushed &middot; re-review triggered</span>
419
+ <span class="tl" style="transition-delay:.55s"><span class="c2">recon</span> new finding &middot; stale id still collides on redelivery</span>
420
+ <span class="tl" style="transition-delay:.8s"><span class="ac">sortie 2</span> root cause removed &middot; the colliding ids no longer exist</span>
421
+ <span class="tl" style="transition-delay:1.05s"><span class="c2">assess</span> score <span class="ok">5/5</span> &middot; current head &middot; CI green &middot; threads resolved</span>
422
+ <span class="tl" style="transition-delay:1.3s"><span class="ok">cleared</span> gate unlocks &middot; landing pinned to the reviewed SHA</span></div></div>
423
+ </div></section>
424
+
425
+ <section class="stage-wrap">
426
+ <canvas class="scene" data-scene="gatewall"></canvas>
427
+ <div class="shead rv"><div class="eyebrow">the gate</div><h2>Approved, or it dies.</h2><p>Every run has to prove the current head: GitHub review approval, green CI, resolved threads, and a SHA-pinned merge. Proof fails &mdash; the cnet says so and hands you the next verb. And completion is evidence, not a claim: no commits, no diff, no PR &mdash; no <i>complete</i>. A drone that can&rsquo;t prove its work says needs-attention, on the record.</p></div>
428
+ <div class="split rv">
429
+ <div><h3>The gate pins the exact head.</h3>
430
+ <p><code>@rig/pr-review-plugin</code> keeps pushing the same PR through actionable feedback until the evidence is clean. The merge uses <code>--match-head-commit</code>: push one more commit after approval and the merge fails instead of shipping an unreviewed head.</p>
431
+ <a class="btn" href="docs/gate/index.html" style="margin-top:8px">Read the gate spec</a></div>
432
+ <div class="panel"><div class="pb">strict gate &middot; PR #418</div><div class="pp"><span class="c2">attempt 1</span> review <span class="mg">changes requested</span> &middot; CI red <span class="dim">&rarr; steer run</span>
433
+ <span class="c2">attempt 2</span> review pending &middot; <span class="mg">1 thread open</span> <span class="dim">&rarr; continue</span>
434
+ <span class="c2">attempt 3</span> review <span class="ok">approved</span> &middot; CI green &middot; threads resolved <span class="ok">&#10003;</span>
435
+ <span class="ac">merge</span> gh pr merge <span class="dim">--match-head-commit</span> 9f3c2e1 <span class="ok">&#10003;</span>
436
+ <span class="ok">closed</span> issue #418 &middot; branch deleted</div></div>
437
+ </div></section>
438
+
439
+ <section id="attach" class="stage-wrap">
440
+ <canvas class="scene" data-scene="harness"></canvas>
441
+ <div class="shead rv"><div class="eyebrow">operate</div><h2>When a run needs you, it says so.</h2><p>Runs, approvals, and questions surface as attention cards in the cnet. <code>/fleet</code> shows the whole board; <code>/drone [ref]</code> opens one run &mdash; status, progress, timeline, controls, attach.</p></div>
442
+ <div class="split rv">
443
+ <div><h3>Your decision stays in the session.</h3>
444
+ <p>Steer, switch models, or unblock a drone without leaving chat. Need hands-on? <code>rig attach &lt;run-id&gt;</code> or the drone card drops your terminal or browser into that exact run &mdash; credentials stay Rig&rsquo;s problem, not your clipboard&rsquo;s.</p>
445
+ <p>Every action leaves its receipt in the transcript. And when something looks wrong, <code>rig doctor</code> reads the evidence before you reach for kill.</p>
446
+ <a class="btn" href="docs/operator/index.html" style="margin-top:8px">Read the operator guide</a></div>
447
+ <div class="pi-term" data-title="Attention &rarr; /drone &mdash; OMP live session" data-rows="14">
448
+ <script type="application/json">[
449
+ {"t":"dim","s":"⠹ opening Rig session"},
450
+ {"t":"out","s":"● cnet ready — the fleet in your transcript"},
451
+ {"t":"code","s":"Fleet · 4 runs · 1 active · 1 needs attention"},
452
+ {"t":"code","s":"Attention · run 0xA1 parked after recovery attempt"},
453
+ {"t":"code","s":" Send direction · Restart run · Stop run"},
454
+ {"t":"u","s":"/drone 0xA1"},
455
+ {"t":"code","s":"Run 0xA1 · needs attention · remote · recent timeline"},
456
+ {"t":"u","s":"/cnet-action 1"},
457
+ {"t":"u","s":"use the existing retry helper; do not add a new loop"},
458
+ {"t":"think","ms":1400},
459
+ {"t":"out","s":"receipt — direction delivered to run 0xA1."},
460
+ {"t":"dim","s":"inline drone card: status · progress · timeline · controls · attach"},
461
+ {"t":"pause","ms":3200}
462
+ ]</script>
463
+ <div class="pi-term-status-data" hidden data-path="~/work/app (run/0xA1)" data-stats="&uarr;21k &darr;679 R161k $0.000 (sub) 18.4%/128k (auto)" data-model="team-default &bull; thinking high"></div>
464
+ </div>
465
+ </div></section>
466
+
467
+ <section class="stage-wrap">
468
+ <canvas class="scene" data-scene="arsenal"></canvas>
469
+ <div class="shead rv"><div class="eyebrow">the arsenal</div><h2>Verbs for you. Tools for <span class="ac">them.</span></h2><p>The operator gets verbs in the cnet. The Swarm Commander gets the same verbs as tools. Every drone carries a typed mission pack &mdash; no memorized CLI strings, no shell games.</p></div>
470
+ <div class="pkgs rv">
471
+ <a class="pk" href="docs/operator/index.html"><div class="nm">operator deck</div><div class="ds">Inline /fleet, /tasks, /drone and attention cards &middot; attach to any run &middot; rig doctor.</div></a>
472
+ <a class="pk" href="docs/swarm-commander/index.html"><div class="nm">commander deck</div><div class="ds">Reads the cnet &middot; every plugin-contributed verb &middot; a receipt on every move.</div></a>
473
+ <a class="pk" href="docs/agent-tools/index.html"><div class="nm">drone deck</div><div class="ds">rig_task, rig_git, rig_repo_sync, rig_profile, rig_review, irc &mdash; strict schemas that refuse instead of guessing.</div></a>
474
+ </div></section>
475
+
476
+ <section class="stage-wrap">
477
+ <canvas class="scene" data-scene="journal"></canvas>
478
+ <div class="shead rv"><div class="eyebrow">the record</div><h2>Every run is a session record.</h2><p>A run is a detached session: <code>runId === sessionId</code>, and the whole mission &mdash; every message, decision, and receipt &mdash; lives in that drone&rsquo;s journal. The cnet is the live view; the journal is the truth.</p></div>
479
+ <div class="split rev rv">
480
+ <div><h3>The record outlives the wire.</h3>
481
+ <p>Runs record themselves: lifecycle, messages, and receipts append to the owning session&rsquo;s journal as they happen. The cnet projects it live &mdash; a view, never a second truth.</p>
482
+ <p>Reopen <code>rig</code> and the fleet is rediscovered from the records. If a run stalls, <code>rig doctor</code> shows you the evidence before you kill anything.</p>
483
+ <a class="btn" href="docs/runs/index.html" style="margin-top:8px">The run lifecycle</a></div>
484
+ <div class="panel"><div class="pb">cnet &rarr; records &rarr; drone card</div><div class="pp"><span class="dim">10:42:01</span> <span class="c2">open</span> session open &middot; Rig aboard
485
+ <span class="dim">10:42:04</span> <span class="c2">session</span> run 0xA1 ready <span class="ok">&#10003;</span>
486
+ <span class="dim">10:43:18</span> <span class="w">run</span> implementing &middot; worktree isolated
487
+ <span class="dim">10:44:02</span> <span class="mg">receipt</span> direction accepted: &ldquo;prefer the existing retry helper&rdquo;
488
+ <span class="dim">10:51:40</span> <span class="ac">attention</span> approval resolved <span class="ok">&#10003;</span>
489
+ <span class="dim">11:02:13</span> <span class="ac">attach</span> terminal attached &middot; run <span class="w">0xA1</span>
490
+ <span class="dim">11:02:55</span> <span class="ok">gate</span> strict gate passed <span class="ok">&#10003;</span>
491
+
492
+ <span class="dim">// one timeline &middot; the whole mission on the record</span></div></div>
493
+ </div></section>
494
+
495
+ <section class="stage-wrap">
496
+ <canvas class="scene" data-scene="cipher"></canvas>
497
+ <div class="shead rv"><div class="eyebrow">the cockpit</div><h2>Address the drone, not the <span class="ac">credential.</span></h2><p>Pick a run by id &mdash; Rig finds it, holds the credential, and puts you in. Terminal or browser, the join secret never touches your command line or your chat history.</p></div>
498
+ <div class="split rv">
499
+ <div><h3>Any machine is a runway.</h3>
500
+ <p>Placement is a parameter: the same drone flies on your laptop or a remote box. Same run id, same journal, same verbs &mdash; the only difference is where the engine spins.</p>
501
+ <div class="panel"><div class="pb">attach &middot; run 0xE4</div><div class="pp"><span class="c2">target</span> detached drone 0xE4 <span class="dim">&middot; live</span>
502
+ <span class="c2">surface</span> terminal or web
503
+ <span class="c2">credential</span> resolved privately by Rig
504
+ <span class="ok">receipt</span> attached &middot; you&rsquo;re in the cockpit</div></div>
505
+ </div></section>
506
+
507
+ <section id="ops" class="stage-wrap">
508
+ <canvas class="scene" data-scene="heal"></canvas>
509
+ <div class="shead rv"><div class="eyebrow">ops</div><h2>Drop the relay. The run keeps writing.</h2><p>The mission journal rides in the airframe, not on the wire. Drop the relay or close an attached client and the run flies on; reopen ground control and the cnet rebuilds from the record.</p></div>
510
+ <div class="split rev rv">
511
+ <div><h3>The run is its own black box.</h3>
512
+ <p>The detached session is the authority on itself. Close a terminal, refresh the browser, lose the network &mdash; reconnect and everything is still there, because nothing was ever only on the wire.</p>
513
+ <p>And a frozen session gets no quiet burial &mdash; <code>rig doctor</code> lays out discovery, relay, transcript, and sandbox evidence for the post-mortem before you kill it.</p></div>
514
+ <div class="panel"><div class="pb">relay blip during a live session &middot; what actually happens</div><div class="pp"><span class="dim">// network drops while OMP keeps the transcript open</span>
515
+ <span class="c2">relay</span> reconnecting to wss://where.rig-does.work <span class="ok">&#10003;</span>
516
+ <span class="c2">session</span> OMP transcript unchanged <span class="dim">(authority local)</span>
517
+ <span class="c2">cnet</span> 0xA1 redisplayed <span class="dim">&middot; rebuilt from the journal</span>
518
+ <span class="w">operator</span> reattaches by run id <span class="dim">&middot; same detached session</span>
519
+ <span class="ok">live</span> OMP authority never moved
520
+
521
+ <span class="dim">// private credential rotated? discovery resolves it again</span>
522
+ <span class="dim">// run gone? rig doctor says that plainly instead of faking attach</span>
523
+ <span class="c2">stall</span> no transcript movement &rarr; diagnostics &middot; operator choice</div></div>
524
+ </div></section>
525
+
526
+ <section class="stage-wrap">
527
+ <canvas class="scene" data-scene="engine"></canvas>
528
+ <div class="shead rv"><div class="eyebrow">the doctrine</div><h2>Everything is a <span class="ac">plugin.</span></h2><p>Rig is a stack of plugins over a three-package floor. The entrypoint, the cnet, the gate, dispatch &mdash; all plugin contributions. Don&rsquo;t like a layer? Replace it, and forge a binary with your swap baked in.</p></div>
529
+ <div class="pkgs rv">
530
+ <a class="pk" href="docs/packages/index.html"><div class="nm">@rig/contracts</div><div class="ds">The floor: schemas, types, IPC shapes. One of only three non-plugins.</div></a>
531
+ <a class="pk" href="docs/packages/index.html"><div class="nm">@rig/core</div><div class="ds">The floor: defineConfig / definePlugin and the plugin host.</div></a>
532
+ <a class="pk" href="docs/packages/index.html"><div class="nm">@rig/kernel-seed</div><div class="ds">The floor: bootstrap seed, plugin ABI, capability resolver.</div></a>
533
+ <a class="pk" href="docs/packages/index.html"><div class="nm">apps/rig</div><div class="ds">The sole product application and first-party plugin graph.</div></a>
534
+ <a class="pk" href="docs/packages/index.html"><div class="nm">@rig/product-entrypoint-plugin</div><div class="ds">Bare rig: session launch, resume, help, history.</div></a>
535
+ <a class="pk" href="docs/packages/index.html"><div class="nm">@rig/omp-extension-plugin</div><div class="ds">The cnet runtime: inline cards, model control, attach.</div></a>
536
+ <a class="pk" href="docs/packages/index.html"><div class="nm">@rig/run-plugin</div><div class="ds">Run journal, read model, cnet projections, run controls.</div></a>
537
+ </div></section>
538
+
539
+ <section class="stage-wrap">
540
+ <canvas class="scene" data-scene="surfaces"></canvas>
541
+ <div class="shead rv"><div class="eyebrow">extend</div><h2>Two extension surfaces. One Rig session.</h2><p>Two places to plug in &mdash; the workflow automation Rig runs around the session, or the OMP session itself.</p></div>
542
+ <div class="split rv">
543
+ <div><h3>Choose the layer that owns the behavior.</h3>
544
+ <p><b>Rig plugins</b> extend the workflow automation layer &mdash; <code>definePlugin</code> from <code>@rig/core</code>, contract-tested, validators dispatch in-process, skills materialize into <code>.pi/skills/</code>.</p>
545
+ <p><b>OMP/Pi extensions</b> extend the live session &mdash; community npm via <code>rig pi add</code> or team-wide <code>runtime.pi.packages</code>, installed at session start.</p>
546
+ <a class="btn" href="plugins/index.html" style="margin-top:8px;margin-right:10px">Plugin authoring</a><a class="btn" href="docs/pi-extensions/index.html" style="margin-top:8px">OMP/Pi extensions</a></div>
547
+ <div class="panel"><div class="pb">rig pi &mdash; OMP/Pi packages</div><div class="pp"><span class="ac">$</span> rig pi search subagents
548
+ <span class="c2">pi-subagents</span> <span class="dim">Subagent orchestration for Pi</span>
549
+ <span class="ac">$</span> rig pi add pi-subagents
550
+ Added pi-subagents to .pi/settings.json.
551
+ <span class="dim">Pi installs missing packages automatically at
552
+ the next session start (local and worker).</span>
553
+ <span class="ac">$</span> rig pi list
554
+ Project OMP/Pi packages (.pi/settings.json):
555
+ pi-subagents
556
+ pi-web-access@1.2.0 <span class="dim">[from rig.config runtime.pi.packages]</span></div></div>
557
+ </div></section>
558
+
559
+ <section class="stage-wrap">
560
+ <canvas class="scene" data-scene="swap"></canvas>
561
+ <div class="shead rv"><div class="eyebrow">defaults</div><h2>Opinionated defaults. Zero lock-in.</h2><p>Rig works the second you run it &mdash; and every layer underneath is a contribution you can swap. Keep ours, or replace any of them.</p></div>
562
+ <div class="split rev rv">
563
+ <div><h3>Replace any part. Keep the rest.</h3>
564
+ <p>The defaults aren&rsquo;t hardcoded &mdash; even GitHub Issues ships as the <code>rig-standard</code> plugin and resolves through the same factory as any custom kind. Keep the Rig session, or swap workflow automation pieces deliberately: task source, model, validators, reviewer, sandbox policy, extension packages.</p></div>
565
+ <div class="panel"><div class="pb">default &rarr; swappable</div><div class="pp"><span class="c2">task source</span> github-issues <span class="dim">&rarr;</span> <span class="w">linear &middot; your kind</span>
566
+ <span class="c2">session</span> OMP/Pi <span class="dim">&rarr;</span> <span class="w">runtime.pi.packages</span>
567
+ <span class="c2">model</span> per role <span class="dim">&rarr;</span> <span class="w">runtime.agentRoles</span>
568
+ <span class="c2">validators</span> std:typecheck <span class="dim">&rarr;</span> <span class="w">your plugin validators</span>
569
+ <span class="c2">reviewer</span> github <span class="dim">&rarr;</span> <span class="w">review.provider</span>
570
+ <span class="c2">sandbox</span> enforce <span class="dim">&rarr;</span> <span class="w">workspace.sandbox</span></div></div>
571
+ </div></section>
572
+
573
+ <section class="stage-wrap">
574
+ <canvas class="scene" data-scene="forge"></canvas>
575
+ <div class="shead rv"><div class="eyebrow">the power path</div><h2>Your repo raises its own drone.</h2><p>Drop <code>.rig/rig.config.ts</code> into a project and rig doesn&rsquo;t load plugins &mdash; it <b>builds an airframe</b>: a project-flavored rig, compiled once with your plugins baked in, cached by config + lockfile fingerprint at <code>.rig/rig</code>.</p></div>
576
+ <div class="split rev rv">
577
+ <div><h3>Compiled, not configured.</h3>
578
+ <p>The global binary detects the code config, forges <code>.rig/rig</code> with your validators, task sources, and hooks compiled into the binary itself, then hands the invocation over. Every drone dispatched in that repo flies <b>your</b> airframe &mdash; the per-run toolchain materializes as hardlinks of it, so the whole sortie runs one build of one binary.</p>
579
+ <p>Change the config or bump a plugin: the lockfile shifts the fingerprint and the next launch re-forges. Delete the config: back to the standard airframe. No daemon, no registry, no drift.</p></div>
580
+ <div class="panel"><div class="pb">power path &mdash; self-compilation</div><div class="pp"><span class="ac">$</span> rig
581
+ <span class="c2">&rsaquo; detect</span> .rig/rig.config.ts <span class="dim">&middot; code config, no upward walk</span>
582
+ <span class="c2">&rsaquo; forge</span> building project binary <span class="w">.rig/rig</span> <span class="dim">&middot; plugins baked in</span>
583
+ <span class="c2">&rsaquo; cache</span> fingerprint = rig.config.ts + bun.lock
584
+ <span class="ac">&rsaquo; exec</span> .rig/rig <span class="dim">&mdash; your airframe, this invocation</span>
585
+
586
+ <span class="dim">// drones here inherit it: rig-run &rarr; hardlink of .rig/rig</span></div></div>
587
+ </div></section>
588
+
589
+ <section class="dog stage-wrap">
590
+ <canvas class="scene" data-scene="ouro"></canvas>
591
+ <div class="rv"><div class="eyebrow" style="margin-bottom:18px">dogfood</div>
592
+ <h2>Rig ships <span class="ac">Rig.</span></h2>
593
+ <p>The drones you just met build the engine you just read about. Rig&rsquo;s own backlog flies through the same session, the same cnet, the same gate &mdash; sorties dispatched, reviews fought, merges pinned to reviewed SHAs. The receipts are in the journals, not in the marketing.</p></div>
594
+ </section>
595
+
596
+ <section class="ctaband stage-wrap">
597
+ <canvas class="scene" data-scene="converge"></canvas>
598
+ <p class="hsub rv"><span class="hsub-a">Rig operators aren&rsquo;t writing code.</span><br><span class="hsub-b"><span class="l2"><span class="l2p">They&rsquo;re </span><span class="l2h">ripping their backlogs apart</span><span class="l2p">.</span></span><canvas class="ripcv" data-pre="They&rsquo;re " data-hot="ripping their backlogs apart" data-post="." aria-hidden="true"></canvas></span></p>
599
+ <div class="rv" style="margin-top:36px;font-family:var(--mono);font-size:14px;color:var(--ink-2)"><span style="color:var(--ac)">$</span> curl -fsSL https://where.rig-does.work/install | bash</div>
600
+ <div class="cta rv" style="justify-content:center;margin-top:38px"><a class="btn pri" href="docs/index.html">Start building</a><a class="btn" href="https://github.com/humanity-org/rig">View source</a></div>
601
+ </section>
602
+
603
+ <footer><div class="foot">
604
+ <div class="col"><h5>orchestrate</h5><a href="docs/getting-started/index.html">getting started</a><a href="docs/runs/index.html">the run lifecycle</a><a href="docs/operator/index.html">operator guide</a><a href="docs/gate/index.html">the merge gate</a></div>
605
+ <div class="col"><h5>extend</h5><a href="plugins/index.html">plugin authoring</a><a href="skills/index.html">skills</a><a href="docs/pi-extensions/index.html">OMP/Pi extensions</a><a href="docs/packages/index.html">packages</a></div>
606
+ <div class="col"><h5>operate</h5><a href="docs/index.html">docs home</a><a href="cli/index.html">cli reference</a><a href="config/index.html">rig.config.ts</a></div>
607
+ <div class="col"><h5>source</h5><a href="https://github.com/humanity-org/rig">github</a><a href="https://where.rig-does.work/install">install script</a></div>
608
+ <div class="sig">Rig is the OMP session extension.<br><b>You are the operator.</b><br><br>release: rig --version</div>
609
+ </div></footer><script>
610
+ /* ============================================================
611
+ PER-SECTION SCENE ENGINE
612
+ Each <canvas class="scene" data-scene="X"> gets a bespoke
613
+ animated abstraction of what its section DOES. Only the
614
+ scenes whose section is on-screen run rAF (perf + intent).
615
+ ============================================================ */
616
+ (function(){
617
+ var AC=[204,255,77], C2=[86,216,255], MG=[255,121,176];
618
+ function rgba(c,a){return 'rgba('+c[0]+','+c[1]+','+c[2]+','+a+')';}
619
+ var pointer={x:-1e4,y:-1e4,active:false};
620
+ addEventListener('mousemove',function(e){pointer.x=e.clientX;pointer.y=e.clientY;pointer.active=true;},{passive:true});
621
+
622
+ // ---- scene base: handles sizing, local pointer, lifecycle ----
623
+ // Every scene gets two free richness layers from the base:
624
+ // self.dim(a) — translucent dark fill instead of clearRect: persistence
625
+ // trails turn every moving particle into a comet.
626
+ // depth field — a parallax layer of drifting, twinkling motes painted
627
+ // BEHIND the scene art via destination-over compositing.
628
+ function Scene(canvas, build){
629
+ var ctx=canvas.getContext('2d'),DPR=Math.min(devicePixelRatio||1,2),W=0,H=0,t=0,running=false,inited=false;
630
+ var self={canvas:canvas,ctx:ctx,W:0,H:0,t:0,mx:-1e4,my:-1e4};
631
+ var motes=[],dimmed=false;
632
+ function seedMotes(){motes=[];var n=Math.min(120,Math.round(W*H/22000));for(var i=0;i<n;i++)motes.push({x:Math.random()*W,y:Math.random()*H,z:.25+.75*Math.random(),sz:.5+Math.random()*1.3,tw:Math.random()*6.28,hue:Math.random()<.18?C2:[170,176,190]});}
633
+ function drawMotes(){for(var i=0;i<motes.length;i++){var m=motes[i];m.x+=.05*m.z;if(m.x>W+4)m.x=-4;
634
+ var a=(.04+.07*m.z)*(0.55+0.45*Math.sin(t*.02+m.tw));
635
+ ctx.fillStyle=rgba(m.hue,a);ctx.beginPath();ctx.arc(m.x,m.y,m.sz*m.z,0,7);ctx.fill();}}
636
+ self.dim=function(a){dimmed=true;ctx.fillStyle='rgba(7,8,9,'+a+')';ctx.fillRect(0,0,W,H);drawMotes();};
637
+ function size(){var r=canvas.getBoundingClientRect();W=Math.max(1,r.width);H=Math.max(1,r.height);canvas.width=W*DPR;canvas.height=H*DPR;ctx.setTransform(DPR,0,0,DPR,0,0);self.W=W;self.H=H;seedMotes();}
638
+ var api;
639
+ function ensure(){if(!inited){size();api=build(self,ctx,rgba,{AC:AC,C2:C2,MG:MG});inited=true;if(api&&api.resize)api.resize();}}
640
+ function localPointer(){var r=canvas.getBoundingClientRect();if(pointer.active&&pointer.x>=r.left&&pointer.x<=r.right&&pointer.y>=r.top&&pointer.y<=r.bottom){self.mx=pointer.x-r.left;self.my=pointer.y-r.top;}else{self.mx=-1e4;self.my=-1e4;}}
641
+ function depth(){ctx.save();ctx.globalCompositeOperation='destination-over';drawMotes();ctx.restore();}
642
+ function frame(){if(!running)return;t++;self.t=t;var r=canvas.getBoundingClientRect();if(Math.abs(r.width-W)>1||Math.abs(r.height-H)>1){if(r.width>0&&r.height>0){size();if(api&&api.resize)api.resize();}}localPointer();dimmed=false;if(api){try{api.draw();}catch(e){if(api.resize)api.resize();}}if(!dimmed)depth();requestAnimationFrame(frame);}
643
+ addEventListener('resize',function(){if(inited){size();if(api.resize)api.resize();}});
644
+ return {start:function(){ensure();if(!running){running=true;requestAnimationFrame(frame);}},stop:function(){running=false;}};
645
+ }
646
+
647
+ // =========================================================
648
+ // SCENE 1 — HERO: SESSION SWARM
649
+ // A bright session core emits projection packets that stream outward
650
+ // along curved lanes into the page. Cursor bends the swarm.
651
+ // "Bare rig opens the session."
652
+ // =========================================================
653
+ function buildDispatch(s,ctx,rgba,C){
654
+ var core={x:0,y:0}, lanes=[], drones=[], waves=[];
655
+ function setup(){
656
+ core.x=s.W*0.30; core.y=s.H*0.46;
657
+ lanes=[]; var N=11;
658
+ for(var i=0;i<N;i++){var ang=(-0.9+1.8*(i/(N-1)))*(Math.PI*0.5);lanes.push({ang:ang,len:s.W*(0.55+Math.random()*0.45),curve:(Math.random()-.5)*0.9});}
659
+ drones=[];for(var k=0;k<130;k++)spawn(true);
660
+ }
661
+ function spawn(rand){var l=(Math.random()*lanes.length)|0;drones.push({lane:l,p:rand?Math.random():0,spd:.0016+Math.random()*.0032,sz:1+Math.random()*2.4,hue:Math.random()<.22?C.C2:C.AC,a:.18+Math.random()*.5,w:(Math.random()-.5)*40});}
662
+ function pt(l,p){var L=lanes[l];var dist=L.len*p;var bend=Math.sin(p*Math.PI)*L.curve*180;var ux=Math.cos(L.ang),uy=Math.sin(L.ang);var px=-uy,py=ux;var x=core.x+ux*dist+px*bend,y=core.y+uy*dist+py*bend;return{x:x,y:y};}
663
+ return {resize:setup, draw:function(){
664
+ s.dim(.26);
665
+ // cursor nudges core
666
+ if(s.mx>-1e3){var dx=s.mx-core.x,dy=s.my-core.y,d=Math.hypot(dx,dy);if(d<360){core.x+=dx/d*Math.min(18,(360-d)*.05);core.y+=dy/d*Math.min(18,(360-d)*.05);} }
667
+ core.x+=(s.W*0.30-core.x)*.04; core.y+=(s.H*0.46-core.y)*.04;
668
+ // lanes (faint)
669
+ ctx.lineWidth=1;
670
+ for(var i=0;i<lanes.length;i++){ctx.beginPath();var seg=26;for(var q=0;q<=seg;q++){var P=pt(i,q/seg);if(q===0)ctx.moveTo(P.x,P.y);else ctx.lineTo(P.x,P.y);}var g=ctx.createLinearGradient(core.x,core.y,core.x+Math.cos(lanes[i].ang)*lanes[i].len,core.y+Math.sin(lanes[i].ang)*lanes[i].len);g.addColorStop(0,rgba(C.AC,.06));g.addColorStop(1,rgba(C.AC,0));ctx.strokeStyle=g;ctx.stroke();}
671
+ // drones
672
+ for(var k=drones.length-1;k>=0;k--){var dr=drones[k];dr.p+=dr.spd;if(dr.p>=1){drones.splice(k,1);spawn(false);continue;}var P=pt(dr.lane,dr.p);P.y+=dr.w*Math.sin(dr.p*6.28);var fade=dr.p<.1?dr.p/.1:(dr.p>.85?(1-dr.p)/.15:1);var a=dr.a*fade;ctx.fillStyle=rgba(dr.hue,a);ctx.beginPath();ctx.arc(P.x,P.y,dr.sz,0,7);ctx.fill();ctx.fillStyle=rgba(dr.hue,a*.12);ctx.beginPath();ctx.arc(P.x,P.y,dr.sz*3.4,0,7);ctx.fill();}
673
+ // dispatch shockwaves — periodic launch pulse rippling out of the core
674
+ if(s.t%150===0)waves.push({r:4});
675
+ for(var w=waves.length-1;w>=0;w--){var wv=waves[w];wv.r+=3.4;var wa=.3*(1-wv.r/(s.W*.42));
676
+ if(wa<=0){waves.splice(w,1);continue;}
677
+ ctx.strokeStyle=rgba(C.AC,wa);ctx.lineWidth=1.2;ctx.beginPath();ctx.arc(core.x,core.y,wv.r,0,7);ctx.stroke();}
678
+ // radar sweep rings around the core
679
+ ctx.save();ctx.translate(core.x,core.y);
680
+ ctx.rotate(s.t*.004);ctx.setLineDash([2,15]);ctx.strokeStyle=rgba(C.AC,.20);ctx.lineWidth=1;ctx.beginPath();ctx.arc(0,0,46,0,7);ctx.stroke();
681
+ ctx.rotate(-s.t*.011);ctx.strokeStyle=rgba(C.C2,.13);ctx.beginPath();ctx.arc(0,0,74,0,7);ctx.stroke();
682
+ ctx.setLineDash([]);ctx.restore();
683
+ // core glow
684
+ var pl=.7+.3*Math.sin(s.t*.05);
685
+ var gg=ctx.createRadialGradient(core.x,core.y,0,core.x,core.y,90);gg.addColorStop(0,rgba(C.AC,.5*pl));gg.addColorStop(.4,rgba(C.AC,.12*pl));gg.addColorStop(1,rgba(C.AC,0));ctx.fillStyle=gg;ctx.fillRect(core.x-90,core.y-90,180,180);
686
+ ctx.fillStyle=rgba(C.AC,.9);ctx.beginPath();ctx.arc(core.x,core.y,3.2,0,7);ctx.fill();
687
+ ctx.strokeStyle=rgba(C.AC,.3*pl);ctx.lineWidth=1;ctx.beginPath();ctx.arc(core.x,core.y,12+5*pl,0,7);ctx.stroke();
688
+ }};
689
+ }
690
+
691
+ // =========================================================
692
+ // SCENE 2 — LOOP: INSTALL TO INLINE ATTENTION
693
+ // Packets flow left->right across the band, passing 5 gate
694
+ // lines; each packet changes color as it clears a step
695
+ // (install -> open -> dispatch -> cnet -> pin). The literal
696
+ // session-first loop.
697
+ // =========================================================
698
+ function buildCNet(s,ctx,rgba,C){
699
+ var packets=[], gates=[], glow=[0,0,0,0,0], baseY;
700
+ var stageHues=[[120,124,134],C.C2,[170,210,120],[255,200,90],C.AC]; // queued->...->done
701
+ function setup(){baseY=s.H*0.5;gates=[];for(var i=0;i<5;i++)gates.push(s.W*(0.12+0.76*(i/4)));packets=[];for(var k=0;k<80;k++){packets.push({x:Math.random()*s.W,y:baseY+(Math.random()-.5)*s.H*0.5,vy:(Math.random()-.5)*0.15,spd:.6+Math.random()*1.0,stage:0,sz:1.4+Math.random()*2.2});}}
702
+ function hueFor(st){return stageHues[Math.max(0,Math.min(4,st))];}
703
+ return {resize:setup, draw:function(){
704
+ s.dim(.3);
705
+ // gate posts — flare when a packet strikes through
706
+ for(var i=0;i<gates.length;i++){var gx=gates[i],pl=.4+.6*Math.abs(Math.sin(s.t*.03+i*.7));glow[i]*=.92;
707
+ ctx.strokeStyle=rgba(C.AC,.05+.05*pl+.3*glow[i]);ctx.lineWidth=1+glow[i];ctx.beginPath();ctx.moveTo(gx,s.H*0.16);ctx.lineTo(gx,s.H*0.84);ctx.stroke();
708
+ if(glow[i]>.04){var gg=ctx.createLinearGradient(gx-22,0,gx+22,0);gg.addColorStop(0,rgba(C.AC,0));gg.addColorStop(.5,rgba(C.AC,.08*glow[i]));gg.addColorStop(1,rgba(C.AC,0));ctx.fillStyle=gg;ctx.fillRect(gx-22,s.H*0.16,44,s.H*0.68);}
709
+ ctx.fillStyle=rgba(C.AC,.35*pl+.5*glow[i]);ctx.beginPath();ctx.arc(gx,s.H*0.16,2+glow[i]*2,0,7);ctx.fill();ctx.beginPath();ctx.arc(gx,s.H*0.84,2+glow[i]*2,0,7);ctx.fill();}
710
+ // packets
711
+ for(var k=0;k<packets.length;k++){var p=packets[k];var prevx=p.x;p.x+=p.spd;p.y+=p.vy;p.y+=( (baseY+(Math.sin(p.x*.004+k)*s.H*0.12)) - p.y)*.02;
712
+ for(var g=0;g<gates.length;g++){if(prevx<gates[g]&&p.x>=gates[g]){p.stage=g+1;glow[g]=Math.min(1,glow[g]+.5);}}
713
+ if(p.x>s.W+10){p.x=-10;p.stage=0;p.y=baseY+(Math.random()-.5)*s.H*0.5;}
714
+ var hue=hueFor(p.stage);var a=.25+.45*(p.stage/5+.3);
715
+ // trail
716
+ ctx.strokeStyle=rgba(hue,a*.3);ctx.lineWidth=p.sz*.8;ctx.beginPath();ctx.moveTo(p.x-10,p.y);ctx.lineTo(p.x,p.y);ctx.stroke();
717
+ ctx.fillStyle=rgba(hue,a);ctx.beginPath();ctx.arc(p.x,p.y,p.sz,0,7);ctx.fill();
718
+ ctx.fillStyle=rgba(hue,a*.14);ctx.beginPath();ctx.arc(p.x,p.y,p.sz*3.2,0,7);ctx.fill();
719
+ }
720
+ }};
721
+ }
722
+
723
+ // =========================================================
724
+ // SCENE 3 — INLINE ATTENTION -> SESSION CORE
725
+ // Left: an ordered lattice (pin verbs and focused views)
726
+ // firing signal pulses rightward into a dense, churning
727
+ // OMP/Pi session core. "OMP owns. Rig surfaces."
728
+ // =========================================================
729
+ function buildHarness(s,ctx,rgba,C){
730
+ var nodes=[],pulses=[],core,flare=0;
731
+ function setup(){nodes=[];var rows=5,cols=3,gx=s.W*0.10,gw=s.W*0.26;for(var r=0;r<rows;r++)for(var c=0;c<cols;c++){nodes.push({x:gx+gw*(c/(cols-1)),y:s.H*(0.22+0.56*(r/(rows-1)))});}core={x:s.W*0.74,y:s.H*0.5};pulses=[];}
732
+ function emit(){var n=nodes[(Math.random()*nodes.length)|0];pulses.push({x:n.x,y:n.y,p:0,from:n});}
733
+ return {resize:setup, draw:function(){
734
+ s.dim(.3);
735
+ if(s.t%7===0)emit();
736
+ // control lattice
737
+ ctx.lineWidth=1;
738
+ for(var i=0;i<nodes.length;i++){var n=nodes[i];ctx.strokeStyle=rgba(C.C2,.2);if(i+1<nodes.length&&(i+1)%3!==0){ctx.beginPath();ctx.moveTo(n.x,n.y);ctx.lineTo(nodes[i+1].x,nodes[i+1].y);ctx.stroke();}if(i+3<nodes.length){ctx.beginPath();ctx.moveTo(n.x,n.y);ctx.lineTo(nodes[i+3].x,nodes[i+3].y);ctx.stroke();}var pl=.5+.5*Math.sin(s.t*.05+i);ctx.fillStyle=rgba(C.C2,.85*pl);ctx.beginPath();ctx.arc(n.x,n.y,4.5,0,7);ctx.strokeStyle=rgba(C.C2,.18);ctx.stroke();ctx.beginPath();ctx.arc(n.x,n.y,2,0,7);ctx.fill();}
739
+ // standing control links (always visible)
740
+ for(var z=0;z<nodes.length;z+=2){ctx.strokeStyle=rgba(C.C2,.05);ctx.lineWidth=1;ctx.beginPath();ctx.moveTo(nodes[z].x,nodes[z].y);ctx.lineTo(core.x,core.y);ctx.stroke();}
741
+ // pulses travel to core
742
+ for(var k=pulses.length-1;k>=0;k--){var pu=pulses[k];pu.p+=.025;var ex=pu.from.x+(core.x-pu.from.x)*pu.p,ey=pu.from.y+(core.y-pu.from.y)*pu.p;if(pu.p>=1){pulses.splice(k,1);flare=1;continue;}ctx.strokeStyle=rgba(C.C2,.25*(1-pu.p));ctx.lineWidth=1;ctx.beginPath();ctx.moveTo(pu.from.x,pu.from.y);ctx.lineTo(ex,ey);ctx.stroke();ctx.fillStyle=rgba(C.C2,.7);ctx.beginPath();ctx.arc(ex,ey,1.8,0,7);ctx.fill();}
743
+ // execution core — churning particle cluster
744
+ flare*=.92;
745
+ var R=54+flare*16;
746
+ var gg=ctx.createRadialGradient(core.x,core.y,0,core.x,core.y,R*2.2);gg.addColorStop(0,rgba(C.AC,.5+flare*.3));gg.addColorStop(.45,rgba(C.AC,.14));gg.addColorStop(1,rgba(C.AC,0));ctx.fillStyle=gg;ctx.fillRect(core.x-R*2.2,core.y-R*2.2,R*4.4,R*4.4);
747
+ for(var q=0;q<64;q++){var a=q/40*6.28+s.t*.02,rr=R*(.4+.6*Math.abs(Math.sin(q*1.7+s.t*.03)));var x=core.x+Math.cos(a)*rr,y=core.y+Math.sin(a)*rr*.8;ctx.fillStyle=rgba(C.AC,.55+flare*.4);ctx.beginPath();ctx.arc(x,y,1.2+flare*1.5,0,7);ctx.fill();}
748
+ ctx.strokeStyle=rgba(C.AC,.35);ctx.lineWidth=1;ctx.beginPath();ctx.arc(core.x,core.y,R,0,7);ctx.stroke();
749
+ }};
750
+ }
751
+
752
+ // =========================================================
753
+ // SCENE 4 — ISOLATION: PER-RUN WORKTREE LANES
754
+ // Strict horizontal lanes separated by hairline barriers;
755
+ // particles run each lane and NEVER cross. Hovering a lane
756
+ // highlights it. "Runs don't collide."
757
+ // =========================================================
758
+ function buildLanes(s,ctx,rgba,C){
759
+ var lanes=[],NL=6;
760
+ function setup(){lanes=[];for(var i=0;i<NL;i++){var y=s.H*(0.16+0.68*(i/(NL-1)));var runners=[];var cnt=8+((Math.random()*5)|0);for(var k=0;k<cnt;k++)runners.push({p:Math.random(),spd:.0009+Math.random()*.0022,sz:1.3+Math.random()*2,hue:Math.random()<.2?C.C2:C.AC});lanes.push({y:y,runners:runners,hot:0});}}
761
+ return {resize:setup, draw:function(){
762
+ s.dim(.34);
763
+ var x0=s.W*0.08,x1=s.W*0.92;
764
+ for(var i=0;i<lanes.length;i++){var L=lanes[i];
765
+ // hover detect
766
+ var hov=(s.my>-1e3 && Math.abs(s.my-L.y)<((s.H*0.68/(NL-1))/2));
767
+ L.hot+=((hov?1:0)-L.hot)*.1;
768
+ // barrier rails (top+bottom of lane)
769
+ var lg=ctx.createLinearGradient(x0,L.y,x1,L.y);lg.addColorStop(0,rgba(C.AC,.02));lg.addColorStop(.5,rgba(C.AC,.12+.12*L.hot));lg.addColorStop(1,rgba(C.AC,.02));ctx.strokeStyle=lg;ctx.lineWidth=1;
770
+ ctx.beginPath();ctx.moveTo(x0,L.y);ctx.lineTo(x1,L.y);ctx.stroke();
771
+ // runners
772
+ for(var k=0;k<L.runners.length;k++){var r=L.runners[k];r.p+=r.spd;if(r.p>1)r.p-=1;var x=x0+(x1-x0)*r.p;var a=(.42+.4*L.hot)* (r.p<.04?r.p/.04:(r.p>.96?(1-r.p)/.04:1));
773
+ ctx.strokeStyle=rgba(r.hue,a*.4);ctx.lineWidth=r.sz*.7;ctx.beginPath();ctx.moveTo(x-9,L.y);ctx.lineTo(x,L.y);ctx.stroke();
774
+ ctx.fillStyle=rgba(r.hue,a+ .25*L.hot);ctx.beginPath();ctx.arc(x,L.y,r.sz,0,7);ctx.fill();
775
+ ctx.fillStyle=rgba(r.hue,(a)*.15);ctx.beginPath();ctx.arc(x,L.y,r.sz*3,0,7);ctx.fill();}
776
+ // lane label tick at start
777
+ ctx.fillStyle=rgba(C.AC,.25+.5*L.hot);ctx.beginPath();ctx.arc(x0,L.y,1.6,0,7);ctx.fill();
778
+ }
779
+ }};
780
+ }
781
+
782
+ // =========================================================
783
+ // SCENE 5 — ENGINE: PACKAGE CONTRIBUTION GRAPH
784
+ // A central hub with 8 contribution sockets on a ring;
785
+ // plugin nodes orbit and dock signal-links into sockets,
786
+ // which light up. Cursor pulls the nearest plugin node.
787
+ // "Packages load into the session." (8 = the 8 contribution types)
788
+ // =========================================================
789
+ function buildEngine(s,ctx,rgba,C){
790
+ var hub,sockets=[],plugs=[],names=8;
791
+ function setup(){hub={x:s.W*0.5,y:s.H*0.5};var R=Math.min(s.W,s.H)*0.16;sockets=[];for(var i=0;i<names;i++){var a=i/names*6.28-1.57;sockets.push({a:a,x:hub.x+Math.cos(a)*R,y:hub.y+Math.sin(a)*R,lit:0});}plugs=[];for(var k=0;k<names;k++){var orb=R*(2.0+Math.random()*1.1);plugs.push({sock:k,ang:Math.random()*6.28,orb:orb,spd:(Math.random()<.5?1:-1)*(.002+Math.random()*.003),docked:0,fire:0});}}
792
+ return {resize:setup, draw:function(){
793
+ s.dim(.3);
794
+ var R=Math.min(s.W,s.H)*0.16;hub.x=s.W*0.5;hub.y=s.H*0.5;
795
+ // socket ring
796
+ ctx.strokeStyle=rgba(C.AC,.14);ctx.lineWidth=1;ctx.beginPath();ctx.arc(hub.x,hub.y,R,0,7);ctx.stroke();
797
+ for(var i=0;i<sockets.length;i++){var sk=sockets[i];sk.x=hub.x+Math.cos(sk.a)*R;sk.y=hub.y+Math.sin(sk.a)*R;sk.lit*=.94;ctx.fillStyle=rgba(C.AC,.3+.7*sk.lit);ctx.beginPath();ctx.arc(sk.x,sk.y,2+2*sk.lit,0,7);ctx.fill();}
798
+ // plugin nodes
799
+ for(var k=0;k<plugs.length;k++){var pg=plugs[k];pg.ang+=pg.spd;var px=hub.x+Math.cos(pg.ang)*pg.orb,py=hub.y+Math.sin(pg.ang)*pg.orb*0.78;
800
+ // cursor pull
801
+ if(s.mx>-1e3){var dx=s.mx-px,dy=s.my-py,d=Math.hypot(dx,dy);if(d<140){px+=dx*.18;py+=dy*.18;}}
802
+ var sk=sockets[pg.sock];var ddock=Math.hypot(px-sk.x,py-sk.y);
803
+ // periodic fire toward socket
804
+ pg.fire*=.9;
805
+ if(s.t%48===((k*6)%48)){pg.fire=1;sk.lit=1;}
806
+ if(pg.fire>.02){ctx.strokeStyle=rgba(C.AC,.4*pg.fire);ctx.lineWidth=1;ctx.beginPath();ctx.moveTo(px,py);ctx.lineTo(sk.x,sk.y);ctx.stroke();var fx=px+(sk.x-px)*(1-pg.fire),fy=py+(sk.y-py)*(1-pg.fire);ctx.fillStyle=rgba(C.AC,.8);ctx.beginPath();ctx.arc(fx,fy,1.8,0,7);ctx.fill();}
807
+ else{ctx.strokeStyle=rgba(C.C2,.09);ctx.lineWidth=1;ctx.beginPath();ctx.moveTo(px,py);ctx.lineTo(sk.x,sk.y);ctx.stroke();}
808
+ var hue=k%4===0?C.C2:C.AC;ctx.fillStyle=rgba(hue,.8);ctx.beginPath();ctx.arc(px,py,3,0,7);ctx.fill();ctx.fillStyle=rgba(hue,.16);ctx.beginPath();ctx.arc(px,py,9,0,7);ctx.fill();}
809
+ // hub
810
+ var pl=.6+.4*Math.sin(s.t*.05);var gg=ctx.createRadialGradient(hub.x,hub.y,0,hub.x,hub.y,56);gg.addColorStop(0,rgba(C.AC,.55*pl));gg.addColorStop(.5,rgba(C.AC,.12));gg.addColorStop(1,rgba(C.AC,0));ctx.fillStyle=gg;ctx.fillRect(hub.x-56,hub.y-56,112,112);
811
+ ctx.fillStyle=rgba(C.AC,.9);ctx.beginPath();ctx.arc(hub.x,hub.y,3,0,7);ctx.fill();
812
+ }};
813
+ }
814
+
815
+ // =========================================================
816
+ // SCENE 6 — CTA: CONVERGENCE
817
+ // Everything (packets from all edges) streams inward and
818
+ // converges to a single bright point. "Open the session."
819
+ // =========================================================
820
+ function buildConverge(s,ctx,rgba,C){
821
+ var pts=[],rings=[],center;
822
+ function setup(){center={x:s.W*0.5,y:s.H*0.5};pts=[];rings=[];for(var k=0;k<90;k++)spawn(true);}
823
+ function spawn(rand){var edge=(Math.random()*4)|0,x,y;if(edge===0){x=Math.random()*s.W;y=-10;}else if(edge===1){x=s.W+10;y=Math.random()*s.H;}else if(edge===2){x=Math.random()*s.W;y=s.H+10;}else{x=-10;y=Math.random()*s.H;}pts.push({x:x,y:y,sx:x,sy:y,p:rand?Math.random():0,spd:.004+Math.random()*.006,hue:Math.random()<.25?C.C2:C.AC,sz:1+Math.random()*1.8});}
824
+ return {resize:setup, draw:function(){
825
+ s.dim(.28);center.x=s.W*0.5;center.y=s.H*0.5;
826
+ for(var k=pts.length-1;k>=0;k--){var p=pts[k];p.p+=p.spd;var e=p.p*p.p;var x=p.sx+(center.x-p.sx)*e,y=p.sy+(center.y-p.sy)*e;if(p.p>=1){pts.splice(k,1);if(Math.random()<.3)rings.push({r:5,a:.4});spawn(false);continue;}var a=.5*(1-Math.abs(p.p-.5)*2)+.1;ctx.strokeStyle=rgba(p.hue,a*.4);ctx.lineWidth=p.sz*.7;var bx=p.sx+(center.x-p.sx)*((p.p-.02)*(p.p-.02));var by=p.sy+(center.y-p.sy)*((p.p-.02)*(p.p-.02));ctx.beginPath();ctx.moveTo(bx,by);ctx.lineTo(x,y);ctx.stroke();ctx.fillStyle=rgba(p.hue,a);ctx.beginPath();ctx.arc(x,y,p.sz,0,7);ctx.fill();}
827
+ // arrival ripples
828
+ for(var r2=rings.length-1;r2>=0;r2--){var rg=rings[r2];rg.r+=1.8;rg.a*=.94;if(rg.a<.02){rings.splice(r2,1);continue;}
829
+ ctx.strokeStyle=rgba(C.AC,rg.a);ctx.lineWidth=1;ctx.beginPath();ctx.arc(center.x,center.y,rg.r,0,7);ctx.stroke();}
830
+ var pl=.6+.4*Math.sin(s.t*.06);var gg=ctx.createRadialGradient(center.x,center.y,0,center.x,center.y,70);gg.addColorStop(0,rgba(C.AC,.45*pl));gg.addColorStop(.4,rgba(C.AC,.1));gg.addColorStop(1,rgba(C.AC,0));ctx.fillStyle=gg;ctx.fillRect(center.x-70,center.y-70,140,140);
831
+ ctx.fillStyle=rgba(C.AC,.95);ctx.beginPath();ctx.arc(center.x,center.y,3.4,0,7);ctx.fill();
832
+ }};
833
+ }
834
+
835
+ // =========================================================
836
+ // SCENE 7 — WALKTHROUGH: RIG BOOT TRACK
837
+ // One bright runner travels a single long track through 10
838
+ // stage beacons; each beacon flares lime as it's cleared and
839
+ // stays lit until the runner loops. "One command opens it."
840
+ // =========================================================
841
+ function buildRelay(s,ctx,rgba,C){
842
+ var beacons=[],runner,N=10,bg=[];
843
+ function setup(){beacons=[];for(var i=0;i<N;i++)beacons.push({x:s.W*(0.06+0.88*(i/(N-1))),y:s.H*0.5,lit:0,hold:false});runner={p:0,spd:.0018};bg=[];for(var k=0;k<26;k++)bg.push({x:Math.random()*s.W,y:s.H*(0.18+Math.random()*0.64),spd:.25+Math.random()*.5,sz:.8+Math.random()*1.3,a:.05+Math.random()*.12});}
844
+ return {resize:setup, draw:function(){
845
+ s.dim(.3);
846
+ // ambient drift
847
+ for(var k=0;k<bg.length;k++){var b=bg[k];b.x+=b.spd;if(b.x>s.W+5)b.x=-5;ctx.fillStyle=rgba(C.C2,b.a);ctx.beginPath();ctx.arc(b.x,b.y,b.sz,0,7);ctx.fill();}
848
+ var y=s.H*0.5,x0=beacons[0].x,x1=beacons[N-1].x;
849
+ // track
850
+ var lg=ctx.createLinearGradient(x0,y,x1,y);lg.addColorStop(0,rgba(C.AC,.04));lg.addColorStop(.5,rgba(C.AC,.14));lg.addColorStop(1,rgba(C.AC,.04));ctx.strokeStyle=lg;ctx.lineWidth=1;ctx.beginPath();ctx.moveTo(x0,y);ctx.lineTo(x1,y);ctx.stroke();
851
+ // runner
852
+ runner.p+=runner.spd;if(runner.p>=1.12){runner.p=-0.04;for(var i=0;i<N;i++)beacons[i].hold=false;}
853
+ var rx=x0+(x1-x0)*Math.min(1,Math.max(0,runner.p));
854
+ // beacons
855
+ for(var i=0;i<N;i++){var bc=beacons[i];if(rx>=bc.x)bc.hold=true;bc.lit+=((bc.hold?1:0)-bc.lit)*.12;
856
+ ctx.strokeStyle=rgba(C.AC,.15+.25*bc.lit);ctx.lineWidth=1;ctx.beginPath();ctx.arc(bc.x,y,5+2*bc.lit,0,7);ctx.stroke();
857
+ ctx.fillStyle=rgba(C.AC,.25+.65*bc.lit);ctx.beginPath();ctx.arc(bc.x,y,2+1.4*bc.lit,0,7);ctx.fill();
858
+ if(bc.lit>.05){ctx.fillStyle=rgba(C.AC,.10*bc.lit);ctx.beginPath();ctx.arc(bc.x,y,14,0,7);ctx.fill();}}
859
+ // runner glow + comet trail
860
+ if(runner.p>=0&&runner.p<=1){ctx.strokeStyle=rgba(C.AC,.35);ctx.lineWidth=2;ctx.beginPath();ctx.moveTo(Math.max(x0,rx-46),y);ctx.lineTo(rx,y);ctx.stroke();
861
+ var gg=ctx.createRadialGradient(rx,y,0,rx,y,26);gg.addColorStop(0,rgba(C.AC,.6));gg.addColorStop(1,rgba(C.AC,0));ctx.fillStyle=gg;ctx.fillRect(rx-26,y-26,52,52);
862
+ ctx.fillStyle=rgba(C.AC,.95);ctx.beginPath();ctx.arc(rx,y,3,0,7);ctx.fill();}
863
+ }};
864
+ }
865
+
866
+ // =========================================================
867
+ // SCENE 8 — STRICT MERGE GATE: THE WALL
868
+ // Packets stream toward a vertical scan wall, queue briefly,
869
+ // get scanned (cyan flash), pass bright lime — or bounce as
870
+ // rejected. "Nothing merges on vibes."
871
+ // =========================================================
872
+ function buildGatewall(s,ctx,rgba,C){
873
+ var pk=[],frags=[],wallX,slotT=0;
874
+ function setup(){wallX=s.W*0.58;pk=[];frags=[];for(var k=0;k<46;k++)spawn(true);}
875
+ function spawn(rand){var bad=Math.random()<.12;pk.push({x:rand?Math.random()*s.W:-8,y:s.H*(0.2+Math.random()*0.6),spd:.7+Math.random()*.9,state:0,wait:0,bad:bad,sz:1.3+Math.random()*1.8});}
876
+ return {resize:setup, draw:function(){
877
+ s.dim(.3);wallX=s.W*0.58;slotT+=.02;
878
+ // the wall — dashed, with a pulsing scan slot
879
+ ctx.strokeStyle=rgba(C.AC,.16);ctx.lineWidth=1;ctx.setLineDash([3,7]);ctx.beginPath();ctx.moveTo(wallX,s.H*0.12);ctx.lineTo(wallX,s.H*0.88);ctx.stroke();ctx.setLineDash([]);
880
+ var pl=.5+.5*Math.sin(slotT*2.4);
881
+ ctx.strokeStyle=rgba(C.AC,.25+.3*pl);ctx.lineWidth=1;ctx.beginPath();ctx.moveTo(wallX,s.H*0.12);ctx.lineTo(wallX,s.H*0.2);ctx.stroke();ctx.beginPath();ctx.moveTo(wallX,s.H*0.8);ctx.lineTo(wallX,s.H*0.88);ctx.stroke();
882
+ ctx.fillStyle=rgba(C.AC,.5+.4*pl);ctx.beginPath();ctx.arc(wallX,s.H*0.12,2,0,7);ctx.fill();ctx.beginPath();ctx.arc(wallX,s.H*0.88,2,0,7);ctx.fill();
883
+ for(var k=pk.length-1;k>=0;k--){var p=pk[k];
884
+ if(p.state===0){p.x+=p.spd;if(p.x>=wallX-14){p.state=1;p.wait=14+Math.random()*30;}}
885
+ else if(p.state===1){p.wait--;p.x+=(wallX-12-p.x)*.1;if(p.wait<=0){p.state=p.bad?3:2;
886
+ if(p.bad)for(var f=0;f<7;f++)frags.push({x:p.x,y:p.y,vx:-(.4+Math.random()*1.6),vy:(Math.random()-.5)*2.4,life:1});}}
887
+ else if(p.state===2){p.x+=p.spd*1.5;}
888
+ else{p.x-=p.spd*1.1;p.y+=.4;}
889
+ if(p.x>s.W+10||p.x<-12){pk.splice(k,1);spawn(false);continue;}
890
+ var hue=p.state===2?C.AC:(p.state===3?C.MG:[150,156,170]);
891
+ var a=p.state===2?.7:(p.state===3?.5:.3);
892
+ if(p.state===1&&p.wait<8){ctx.strokeStyle=rgba(C.C2,.5);ctx.lineWidth=1;ctx.beginPath();ctx.moveTo(wallX,p.y-7);ctx.lineTo(wallX,p.y+7);ctx.stroke();}
893
+ ctx.strokeStyle=rgba(hue,a*.35);ctx.lineWidth=p.sz*.7;ctx.beginPath();ctx.moveTo(p.x-(p.state===3?-8:8),p.y);ctx.lineTo(p.x,p.y);ctx.stroke();
894
+ ctx.fillStyle=rgba(hue,a);ctx.beginPath();ctx.arc(p.x,p.y,p.sz,0,7);ctx.fill();
895
+ ctx.fillStyle=rgba(hue,a*.13);ctx.beginPath();ctx.arc(p.x,p.y,p.sz*3,0,7);ctx.fill();}
896
+ // rejection debris — the wall shatters what it bounces
897
+ for(var q=frags.length-1;q>=0;q--){var fr=frags[q];fr.x+=fr.vx;fr.y+=fr.vy;fr.vy+=.05;fr.life-=.025;
898
+ if(fr.life<=0){frags.splice(q,1);continue;}
899
+ ctx.fillStyle=rgba(C.MG,.55*fr.life);ctx.fillRect(fr.x,fr.y,1.6,1.6);}
900
+ }};
901
+ }
902
+
903
+ // =========================================================
904
+ // SCENE 9 — SESSION RESUME: CUT & RE-KNIT
905
+ // A node chain carries packets; segments randomly "cut"
906
+ // (magenta snap, flow stalls), then a repair pulse re-knits
907
+ // the link (lime flash) and flow resumes. "The session heals."
908
+ // =========================================================
909
+ function buildHeal(s,ctx,rgba,C){
910
+ var nodes=[],pk=[],cut=null,NN=7;
911
+ function setup(){nodes=[];for(var i=0;i<NN;i++)nodes.push({x:s.W*(0.07+0.86*(i/(NN-1))),y:s.H*(0.5+0.16*Math.sin(i*1.9))});pk=[];for(var k=0;k<34;k++)pk.push({seg:(Math.random()*(NN-1))|0,p:Math.random(),spd:.004+Math.random()*.006,sz:1.2+Math.random()*1.7,hue:Math.random()<.2?C.C2:C.AC});cut=null;}
912
+ return {resize:setup, draw:function(){
913
+ s.dim(.34);
914
+ // schedule cuts + repairs
915
+ if(!cut&&s.t%240===120){cut={seg:1+((Math.random()*(NN-3))|0),age:0};}
916
+ if(cut){cut.age++;if(cut.age>150)cut=null;}
917
+ // links
918
+ for(var i=0;i<NN-1;i++){var a=nodes[i],b=nodes[i+1];var isCut=cut&&cut.seg===i&&cut.age<110;var healing=cut&&cut.seg===i&&cut.age>=110;
919
+ if(isCut){var mx=(a.x+b.x)/2,my=(a.y+b.y)/2;var fl=cut.age<14?(1-cut.age/14):0;
920
+ ctx.strokeStyle=rgba(C.MG,.3+.5*fl);ctx.lineWidth=1;
921
+ ctx.beginPath();ctx.moveTo(a.x,a.y);ctx.lineTo(mx-13,my+(Math.random()-.5)*3);ctx.stroke();
922
+ ctx.beginPath();ctx.moveTo(mx+13,my+(Math.random()-.5)*3);ctx.lineTo(b.x,b.y);ctx.stroke();
923
+ if(fl>0){ctx.fillStyle=rgba(C.MG,.6*fl);ctx.beginPath();ctx.arc(mx,my,4+10*(1-fl),0,7);ctx.fill();}}
924
+ else{var hl=healing?(cut.age-110)/40:0;ctx.strokeStyle=rgba(C.AC,.12+.5*hl);ctx.lineWidth=1+hl;ctx.beginPath();ctx.moveTo(a.x,a.y);ctx.lineTo(b.x,b.y);ctx.stroke();
925
+ if(healing){var rp=(cut.age-110)/40,rx=a.x+(b.x-a.x)*rp,ry=a.y+(b.y-a.y)*rp;ctx.fillStyle=rgba(C.AC,.9);ctx.beginPath();ctx.arc(rx,ry,2.6,0,7);ctx.fill();ctx.fillStyle=rgba(C.AC,.2);ctx.beginPath();ctx.arc(rx,ry,9,0,7);ctx.fill();}}}
926
+ // nodes
927
+ for(var i=0;i<NN;i++){var n=nodes[i],pl=.5+.5*Math.sin(s.t*.04+i*1.3);ctx.fillStyle=rgba(C.AC,.3+.4*pl);ctx.beginPath();ctx.arc(n.x,n.y,2.2,0,7);ctx.fill();ctx.strokeStyle=rgba(C.AC,.12);ctx.beginPath();ctx.arc(n.x,n.y,6,0,7);ctx.stroke();}
928
+ // packets — stall at a cut, resume on heal
929
+ for(var k=0;k<pk.length;k++){var q=pk[k];var blocked=cut&&cut.seg===q.seg&&cut.age<110&&q.p>.42;
930
+ if(!blocked){q.p+=q.spd;if(q.p>=1){q.p=0;q.seg=(q.seg+1)%(NN-1);}}else if(q.p>.46)q.p=.46;
931
+ var a=nodes[q.seg],b=nodes[q.seg+1],x=a.x+(b.x-a.x)*q.p,y=a.y+(b.y-a.y)*q.p;
932
+ var al=blocked?.18:.5;
933
+ ctx.fillStyle=rgba(q.hue,al);ctx.beginPath();ctx.arc(x,y,q.sz,0,7);ctx.fill();
934
+ ctx.fillStyle=rgba(q.hue,al*.14);ctx.beginPath();ctx.arc(x,y,q.sz*3,0,7);ctx.fill();}
935
+ }};
936
+ }
937
+
938
+ // =========================================================
939
+ // SCENE 10 — DOGFOOD: OUROBOROS RING
940
+ // Packets circulate a ring; periodically one dives into the
941
+ // hub (a merge), the hub flares, and a fresh packet re-emits
942
+ // onto the ring. The session feeds itself. "Rig ships Rig."
943
+ // =========================================================
944
+ function buildOuro(s,ctx,rgba,C){
945
+ var orbs=[],divers=[],hub,R,flare=0;
946
+ function setup(){hub={x:s.W*0.5,y:s.H*0.5};R=Math.min(s.W,s.H)*0.30;orbs=[];for(var k=0;k<42;k++)orbs.push({a:Math.random()*6.28,spd:.004+Math.random()*.004,sz:1.1+Math.random()*1.8,hue:Math.random()<.2?C.C2:C.AC,al:.2+Math.random()*.4});divers=[];}
947
+ return {resize:setup, draw:function(){
948
+ s.dim(.26);hub.x=s.W*0.5;hub.y=s.H*0.5;R=Math.min(s.W,s.H)*0.30;
949
+ // ring
950
+ ctx.strokeStyle=rgba(C.AC,.10);ctx.lineWidth=1;ctx.beginPath();ctx.ellipse(hub.x,hub.y,R,R*.62,0,0,7);ctx.stroke();
951
+ // periodically send one diver inward
952
+ if(s.t%160===0&&orbs.length>20){var o=orbs.splice((Math.random()*orbs.length)|0,1)[0];divers.push({a:o.a,r:1,hue:o.hue});}
953
+ for(var k=divers.length-1;k>=0;k--){var d=divers[k];d.r-=.016;d.a+=.05;
954
+ if(d.r<=.04){divers.splice(k,1);flare=1;orbs.push({a:Math.random()*6.28,spd:.004+Math.random()*.004,sz:1.1+Math.random()*1.8,hue:Math.random()<.2?C.C2:C.AC,al:.2+Math.random()*.4});continue;}
955
+ var x=hub.x+Math.cos(d.a)*R*d.r,y=hub.y+Math.sin(d.a)*R*.62*d.r;
956
+ ctx.fillStyle=rgba(d.hue,.7);ctx.beginPath();ctx.arc(x,y,1.8,0,7);ctx.fill();
957
+ ctx.fillStyle=rgba(d.hue,.12);ctx.beginPath();ctx.arc(x,y,6,0,7);ctx.fill();}
958
+ // orbiters
959
+ for(var k=0;k<orbs.length;k++){var o=orbs[k];o.a+=o.spd;var x=hub.x+Math.cos(o.a)*R,y=hub.y+Math.sin(o.a)*R*.62;
960
+ ctx.fillStyle=rgba(o.hue,o.al);ctx.beginPath();ctx.arc(x,y,o.sz,0,7);ctx.fill();
961
+ ctx.fillStyle=rgba(o.hue,o.al*.14);ctx.beginPath();ctx.arc(x,y,o.sz*3,0,7);ctx.fill();}
962
+ // hub
963
+ flare*=.94;var pl=.6+.4*Math.sin(s.t*.05);
964
+ var gg=ctx.createRadialGradient(hub.x,hub.y,0,hub.x,hub.y,60+30*flare);gg.addColorStop(0,rgba(C.AC,(.45+.4*flare)*pl));gg.addColorStop(.5,rgba(C.AC,.1));gg.addColorStop(1,rgba(C.AC,0));ctx.fillStyle=gg;ctx.fillRect(hub.x-90,hub.y-90,180,180);
965
+ ctx.fillStyle=rgba(C.AC,.95);ctx.beginPath();ctx.arc(hub.x,hub.y,3+2*flare,0,7);ctx.fill();
966
+ }};
967
+ }
968
+
969
+ // =========================================================
970
+ // SCENE 11 — WHAT-IS: SESSION TRIAD
971
+ // Three source nodes (operator / commander / fleet) on the left
972
+ // each stream packets along curved lanes into ONE bright
973
+ // convergence node that flares on arrival. "One session,
974
+ // one reviewed PR."
975
+ // =========================================================
976
+ function buildTriad(s,ctx,rgba,C){
977
+ var src=[],dst,flow=[],flare=0;
978
+ function setup(){
979
+ dst={x:s.W*0.80,y:s.H*0.5};
980
+ src=[];for(var i=0;i<3;i++)src.push({x:s.W*0.16,y:s.H*(0.30+0.20*i),hue:i===1?C.AC:C.C2});
981
+ flow=[];for(var k=0;k<66;k++)spawn(true);
982
+ }
983
+ function spawn(rand){flow.push({i:(Math.random()*3)|0,p:rand?Math.random():0,spd:.004+Math.random()*.006,sz:1+Math.random()*1.7});}
984
+ function pt(i,p){var a=src[i],cx=s.W*0.52,cy=dst.y+(a.y-dst.y)*0.16,iu=1-p;return{x:iu*iu*a.x+2*iu*p*cx+p*p*dst.x,y:iu*iu*a.y+2*iu*p*cy+p*p*dst.y};}
985
+ return {resize:setup, draw:function(){
986
+ s.dim(.3);
987
+ for(var i=0;i<3;i++){ctx.strokeStyle=rgba(src[i].hue,.05);ctx.lineWidth=1;ctx.beginPath();for(var q=0;q<=24;q++){var P=pt(i,q/24);if(q===0)ctx.moveTo(P.x,P.y);else ctx.lineTo(P.x,P.y);}ctx.stroke();
988
+ var pl=.5+.5*Math.sin(s.t*.05+i*2);ctx.fillStyle=rgba(src[i].hue,.5*pl);ctx.beginPath();ctx.arc(src[i].x,src[i].y,3,0,7);ctx.fill();ctx.strokeStyle=rgba(src[i].hue,.2);ctx.beginPath();ctx.arc(src[i].x,src[i].y,8,0,7);ctx.stroke();}
989
+ for(var k=flow.length-1;k>=0;k--){var f=flow[k];f.p+=f.spd;if(f.p>=1){flow.splice(k,1);flare=1;spawn(false);continue;}var P=pt(f.i,f.p),hue=src[f.i].hue,a=.3+.4*Math.sin(f.p*3.14);ctx.fillStyle=rgba(hue,a);ctx.beginPath();ctx.arc(P.x,P.y,f.sz,0,7);ctx.fill();ctx.fillStyle=rgba(hue,a*.13);ctx.beginPath();ctx.arc(P.x,P.y,f.sz*3,0,7);ctx.fill();}
990
+ flare*=.93;var pl=.6+.4*Math.sin(s.t*.06);
991
+ var gg=ctx.createRadialGradient(dst.x,dst.y,0,dst.x,dst.y,72+30*flare);gg.addColorStop(0,rgba(C.AC,(.45+.35*flare)*pl));gg.addColorStop(.45,rgba(C.AC,.1));gg.addColorStop(1,rgba(C.AC,0));ctx.fillStyle=gg;ctx.fillRect(dst.x-104,dst.y-104,208,208);
992
+ ctx.fillStyle=rgba(C.AC,.95);ctx.beginPath();ctx.arc(dst.x,dst.y,3.2+2*flare,0,7);ctx.fill();
993
+ ctx.strokeStyle=rgba(C.AC,.3*pl);ctx.lineWidth=1;ctx.beginPath();ctx.arc(dst.x,dst.y,13+5*flare,0,7);ctx.stroke();
994
+ }};
995
+ }
996
+
997
+ // SCENE 12 — PI CARRIER: one bundled extension, loaded into every session
998
+ function buildCarrier(s,ctx,rgba,C){
999
+ var core,nodes=[],pks=[];
1000
+ function setup(){core={x:s.W*0.5,y:s.H*0.5};var N=7,R=Math.min(s.W,s.H)*0.32;nodes=[];for(var i=0;i<N;i++){var a=i/N*6.28-1.57;nodes.push({x:core.x+Math.cos(a)*R,y:core.y+Math.sin(a)*R*0.92,lit:0});}pks=[];}
1001
+ return {resize:setup, draw:function(){
1002
+ s.dim(.3);core.x=s.W*0.5;core.y=s.H*0.5;var R=Math.min(s.W,s.H)*0.32;
1003
+ if(s.t%13===0){var nn=nodes[(Math.random()*nodes.length)|0];pks.push({n:nn,p:0});}
1004
+ for(var i=0;i<nodes.length;i++){var n=nodes[i],a=i/nodes.length*6.28-1.57;n.x=core.x+Math.cos(a)*R;n.y=core.y+Math.sin(a)*R*0.92;n.lit*=.965;
1005
+ ctx.strokeStyle=rgba(C.AC,.04+.16*n.lit);ctx.lineWidth=1;ctx.beginPath();ctx.moveTo(core.x,core.y);ctx.lineTo(n.x,n.y);ctx.stroke();
1006
+ ctx.fillStyle=rgba(C.AC,.28+.6*n.lit);ctx.beginPath();ctx.arc(n.x,n.y,2.2+1.6*n.lit,0,7);ctx.fill();
1007
+ ctx.strokeStyle=rgba(C.AC,.12+.3*n.lit);ctx.beginPath();ctx.arc(n.x,n.y,7+3*n.lit,0,7);ctx.stroke();}
1008
+ for(var k=pks.length-1;k>=0;k--){var pk=pks[k];pk.p+=.04;if(pk.p>=1){pk.n.lit=1;pks.splice(k,1);continue;}var x=core.x+(pk.n.x-core.x)*pk.p,y=core.y+(pk.n.y-core.y)*pk.p;ctx.fillStyle=rgba(C.AC,.85);ctx.beginPath();ctx.arc(x,y,1.8,0,7);ctx.fill();ctx.fillStyle=rgba(C.AC,.16);ctx.beginPath();ctx.arc(x,y,6,0,7);ctx.fill();}
1009
+ var pl=.6+.4*Math.sin(s.t*.05),gg=ctx.createRadialGradient(core.x,core.y,0,core.x,core.y,62);gg.addColorStop(0,rgba(C.AC,.5*pl));gg.addColorStop(.5,rgba(C.AC,.12));gg.addColorStop(1,rgba(C.AC,0));ctx.fillStyle=gg;ctx.fillRect(core.x-62,core.y-62,124,124);
1010
+ ctx.fillStyle=rgba(C.AC,.95);ctx.beginPath();ctx.arc(core.x,core.y,3.4,0,7);ctx.fill();ctx.strokeStyle=rgba(C.AC,.3*pl);ctx.beginPath();ctx.arc(core.x,core.y,12+5*pl,0,7);ctx.stroke();
1011
+ }};
1012
+ }
1013
+ // SCENE 13 — SWAP: HOT-SWAP RACK ON A LIVE BUS
1014
+ // Six modules seated on a data bus that never stops carrying
1015
+ // packets. Periodically one ejects (sparks, floats off) and a
1016
+ // fresh one drops in and LOCKS (ring flash). While the slot is
1017
+ // empty the bus traffic arcs OVER the gap — swap any part, the
1018
+ // session keeps flowing. "Zero lock-in, zero downtime."
1019
+ function buildSwap(s,ctx,rgba,C){
1020
+ var slots=[],pkts=[],sparks=[],sw=null,N=6,y;
1021
+ function setup(){y=s.H*0.52;slots=[];for(var i=0;i<N;i++)slots.push({hue:i%3===0?C.C2:C.AC,ring:0});
1022
+ pkts=[];for(var k=0;k<26;k++)pkts.push({p:Math.random(),spd:.0018+Math.random()*.003,sz:1+Math.random()*1.6,hue:Math.random()<.25?C.C2:C.AC});
1023
+ sparks=[];sw=null;}
1024
+ function sx(i){return s.W*(0.13+0.74*(i/(N-1)));}
1025
+ return {resize:setup, draw:function(){
1026
+ s.dim(.3);
1027
+ var w=48,h=32;
1028
+ // the bus
1029
+ var lg=ctx.createLinearGradient(s.W*0.06,y,s.W*0.94,y);lg.addColorStop(0,rgba(C.AC,.03));lg.addColorStop(.5,rgba(C.AC,.12));lg.addColorStop(1,rgba(C.AC,.03));
1030
+ ctx.strokeStyle=lg;ctx.lineWidth=1;ctx.beginPath();ctx.moveTo(s.W*0.06,y);ctx.lineTo(s.W*0.94,y);ctx.stroke();
1031
+ // schedule swaps: eject with sparks, drop-in replacement
1032
+ if(!sw&&s.t%180===60){sw={i:(Math.random()*N)|0,age:0};
1033
+ for(var f=0;f<12;f++)sparks.push({x:sx(sw.i)+(Math.random()-.5)*18,y:y,vx:(Math.random()-.5)*2.4,vy:-(.5+Math.random()*2),life:1});}
1034
+ if(sw){sw.age++;if(sw.age>76){slots[sw.i].hue=(Math.random()<.4?C.C2:C.AC);slots[sw.i].ring=1;sw=null;}}
1035
+ // slots
1036
+ for(var i=0;i<N;i++){var X=sx(i),sl=slots[i];
1037
+ // socket frame + connector pins
1038
+ ctx.strokeStyle=rgba(C.AC,.10);ctx.lineWidth=1;ctx.strokeRect(X-w/2,y-h/2,w,h);
1039
+ for(var pn=0;pn<4;pn++){var px=X-w/2+7+(w-14)*(pn/3);ctx.fillStyle=rgba(C.AC,.16);ctx.fillRect(px,y+h/2,1.4,4);}
1040
+ var dy=0,a=1,inA=0;
1041
+ if(sw&&sw.i===i){var p=sw.age/76;if(p<.42){dy=-(p/.42)*46;a=1-p/.42;}else{a=0;inA=(p-.42)/.58;}}
1042
+ // resident (or ejecting) module with heartbeat LED
1043
+ if(a>0.02){var hue=sl.hue,hb=.5+.5*Math.sin(s.t*.1+i*2);
1044
+ ctx.fillStyle=rgba(hue,.10*a);ctx.fillRect(X-w/2+5,y-h/2+5+dy,w-10,h-10);
1045
+ ctx.strokeStyle=rgba(hue,.4*a);ctx.strokeRect(X-w/2+5,y-h/2+5+dy,w-10,h-10);
1046
+ ctx.fillStyle=rgba(hue,(.3+.6*hb)*a);ctx.beginPath();ctx.arc(X,y+dy,1.8,0,7);ctx.fill();}
1047
+ // replacement dropping in
1048
+ if(inA>0){var ny=y-(1-inA)*(h+30);
1049
+ ctx.fillStyle=rgba(C.AC,.12*inA);ctx.fillRect(X-w/2+5,ny-h/2+5,w-10,h-10);
1050
+ ctx.strokeStyle=rgba(C.AC,.5*inA);ctx.strokeRect(X-w/2+5,ny-h/2+5,w-10,h-10);}
1051
+ // lock-ring flash when the new module seats
1052
+ if(sl.ring>.03){sl.ring*=.93;var rr=8+(1-sl.ring)*26;
1053
+ ctx.strokeStyle=rgba(C.AC,.5*sl.ring);ctx.lineWidth=1.2;ctx.beginPath();ctx.arc(X,y,rr,0,7);ctx.stroke();}}
1054
+ // bus packets — they arc OVER an empty slot instead of stopping
1055
+ for(var k=0;k<pkts.length;k++){var q=pkts[k];q.p+=q.spd;if(q.p>1)q.p-=1;var x=s.W*(0.06+0.88*q.p);
1056
+ var lift=0;
1057
+ if(sw){var gx=sx(sw.i),d=Math.abs(x-gx);if(d<w*1.4)lift=-Math.cos(d/(w*1.4)*1.5708)*18;}
1058
+ ctx.fillStyle=rgba(q.hue,.6);ctx.beginPath();ctx.arc(x,y+lift,q.sz,0,7);ctx.fill();
1059
+ ctx.fillStyle=rgba(q.hue,.12);ctx.beginPath();ctx.arc(x,y+lift,q.sz*3,0,7);ctx.fill();}
1060
+ // eject sparks
1061
+ for(var sp=sparks.length-1;sp>=0;sp--){var k2=sparks[sp];k2.x+=k2.vx;k2.y+=k2.vy;k2.vy+=.07;k2.life-=.028;
1062
+ if(k2.life<=0){sparks.splice(sp,1);continue;}
1063
+ ctx.fillStyle=rgba(C.AC,.6*k2.life);ctx.fillRect(k2.x,k2.y,1.5,1.5);}
1064
+ }};
1065
+ }
1066
+
1067
+ // SCENE 14 — SURFACES: workflow automation and OMP session packages, one operator linking them
1068
+ function buildSurfaces(s,ctx,rgba,C){
1069
+ var topY,botY,N=6,pk=[];
1070
+ function setup(){topY=s.H*0.34;botY=s.H*0.66;pk=[];}
1071
+ function nx(i){return s.W*(0.12+0.76*(i/(N-1)));}
1072
+ return {resize:setup, draw:function(){
1073
+ s.dim(.32);topY=s.H*0.34;botY=s.H*0.66;
1074
+ ctx.lineWidth=1;ctx.strokeStyle=rgba(C.C2,.09);ctx.beginPath();ctx.moveTo(s.W*0.1,topY);ctx.lineTo(s.W*0.9,topY);ctx.stroke();
1075
+ ctx.strokeStyle=rgba(C.AC,.09);ctx.beginPath();ctx.moveTo(s.W*0.1,botY);ctx.lineTo(s.W*0.9,botY);ctx.stroke();
1076
+ for(var i=0;i<N;i++){var x=nx(i),pt=.5+.5*Math.sin(s.t*.04+i),pb=.5+.5*Math.sin(s.t*.04+i+2);
1077
+ ctx.fillStyle=rgba(C.C2,.28+.4*pt);ctx.beginPath();ctx.arc(x,topY,2.3,0,7);ctx.fill();
1078
+ ctx.fillStyle=rgba(C.AC,.28+.4*pb);ctx.beginPath();ctx.arc(x,botY,2.3,0,7);ctx.fill();}
1079
+ if(s.t%9===0){var lane=Math.random()<.5;pk.push({lane:lane,p:0,spd:.008+Math.random()*.006,hue:lane?C.C2:C.AC});}
1080
+ if(s.t%64===0){pk.push({link:(Math.random()*N)|0,p:0});}
1081
+ for(var k=pk.length-1;k>=0;k--){var p=pk[k];p.p+=(p.link!==undefined?.022:p.spd);if(p.p>=1){pk.splice(k,1);continue;}
1082
+ if(p.link!==undefined){var x=nx(p.link),y=topY+(botY-topY)*p.p;ctx.strokeStyle=rgba(C.AC,.10);ctx.beginPath();ctx.moveTo(x,topY);ctx.lineTo(x,botY);ctx.stroke();ctx.fillStyle=rgba([234,255,176],.8);ctx.beginPath();ctx.arc(x,y,1.9,0,7);ctx.fill();}
1083
+ else{var x2=s.W*(0.1+0.8*p.p),y2=p.lane?topY:botY;ctx.fillStyle=rgba(p.hue,.7);ctx.beginPath();ctx.arc(x2,y2,1.6,0,7);ctx.fill();ctx.fillStyle=rgba(p.hue,.14);ctx.beginPath();ctx.arc(x2,y2,5,0,7);ctx.fill();}}
1084
+ }};
1085
+ }
1086
+
1087
+ // =========================================================
1088
+ // SCENE 15 — RECORD: SESSION JOURNAL TAPE
1089
+ // Events fall out of the run and lock onto an append-only
1090
+ // tape (ticks only ever ADD on the right; the tape scrolls
1091
+ // left). A cyan replay head periodically sweeps the whole
1092
+ // history, re-lighting every tick it passes. "The cnet replays."
1093
+ // ============================================================
1094
+ function buildJournal(s,ctx,rgba,C){
1095
+ var ticks=[],fall=[],head=null,y,x0,x1,SCROLL=.34;
1096
+ function mkTick(x){return{x:x,h:5+Math.random()*16,hue:Math.random()<.16?C.C2:C.AC,lit:0,a:.22+Math.random()*.3};}
1097
+ function setup(){y=s.H*0.8;x0=s.W*0.06;x1=s.W*0.94;ticks=[];fall=[];head=null;
1098
+ for(var x=x0;x<x1-40;x+=9+Math.random()*26)ticks.push(mkTick(x));}
1099
+ return {resize:setup, draw:function(){
1100
+ s.dim(.3);
1101
+ // tape line
1102
+ var lg=ctx.createLinearGradient(x0,y,x1,y);lg.addColorStop(0,rgba(C.AC,.03));lg.addColorStop(.5,rgba(C.AC,.13));lg.addColorStop(1,rgba(C.AC,.05));
1103
+ ctx.strokeStyle=lg;ctx.lineWidth=1;ctx.beginPath();ctx.moveTo(x0,y);ctx.lineTo(x1,y);ctx.stroke();
1104
+ // append-only: events fall from the run above and lock onto the tape's right end
1105
+ if(s.t%30===0)fall.push({x:x1-24,y:y-s.H*0.2-Math.random()*s.H*0.06,vy:0,hue:Math.random()<.2?C.C2:C.AC});
1106
+ for(var k=fall.length-1;k>=0;k--){var f=fall[k];f.vy+=.14;f.y+=f.vy;
1107
+ if(f.y>=y){fall.splice(k,1);var t2=mkTick(f.x);t2.hue=f.hue;t2.lit=1;ticks.push(t2);continue;}
1108
+ ctx.fillStyle=rgba(f.hue,.7);ctx.beginPath();ctx.arc(f.x,f.y,1.8,0,7);ctx.fill();
1109
+ ctx.fillStyle=rgba(f.hue,.12);ctx.beginPath();ctx.arc(f.x,f.y,6,0,7);ctx.fill();}
1110
+ // tape scrolls left; history never rewrites, it only slides off the edge
1111
+ for(var i=ticks.length-1;i>=0;i--){var tk=ticks[i];tk.x-=SCROLL;if(tk.x<x0){ticks.splice(i,1);continue;}
1112
+ tk.lit*=.95;
1113
+ var edge=Math.min(1,(tk.x-x0)/60)*Math.min(1,(x1-tk.x)/40);
1114
+ var al=(tk.a+ .6*tk.lit)*edge;
1115
+ ctx.strokeStyle=rgba(tk.hue,al);ctx.lineWidth=1.4;
1116
+ ctx.beginPath();ctx.moveTo(tk.x,y-tk.h);ctx.lineTo(tk.x,y+tk.h*.5);ctx.stroke();
1117
+ if(tk.lit>.05){ctx.fillStyle=rgba(tk.hue,.16*tk.lit*edge);ctx.beginPath();ctx.arc(tk.x,y-tk.h*.3,8,0,7);ctx.fill();}}
1118
+ // replay head: a cyan cursor sweeps the laid-down history, re-lighting it
1119
+ if(!head&&s.t%420===200)head={x:x0};
1120
+ if(head){head.x+=(x1-x0)*.006;
1121
+ if(head.x>x1){head=null;}
1122
+ else{for(var j=0;j<ticks.length;j++){if(Math.abs(ticks[j].x-head.x)<4)ticks[j].lit=1;}
1123
+ var hg=ctx.createLinearGradient(head.x,y-s.H*0.22,head.x,y+s.H*0.1);hg.addColorStop(0,rgba(C.C2,0));hg.addColorStop(.5,rgba(C.C2,.5));hg.addColorStop(1,rgba(C.C2,0));
1124
+ ctx.strokeStyle=hg;ctx.lineWidth=1;ctx.beginPath();ctx.moveTo(head.x,y-s.H*0.12);ctx.lineTo(head.x,y+s.H*0.07);ctx.stroke();
1125
+ ctx.fillStyle=rgba(C.C2,.8);ctx.beginPath();ctx.arc(head.x,y,2.2,0,7);ctx.fill();
1126
+ ctx.fillStyle=rgba(C.C2,.14);ctx.beginPath();ctx.arc(head.x,y,9,0,7);ctx.fill();}}
1127
+ }};
1128
+ }
1129
+
1130
+ // =========================================================
1131
+ // SCENE — SWARMER: THE DRONE OF DRONES
1132
+ // A commander drone holds a dashed patrol ring (the standing
1133
+ // order). Worker drones fly loose formation. Event pulses fly
1134
+ // IN from workers; the commander flares awake, banks, and
1135
+ // fires a vector beam back OUT (dispatch/steer/stop). Every
1136
+ // action drops a tick into the cnet strip along the bottom.
1137
+ // =========================================================
1138
+ function buildSwarmer(s,ctx,rgba,C){
1139
+ var cmd={x:0,y:0},workers=[],events=[],beams=[],ticks=[],wake=0;
1140
+ function setup(){cmd.x=s.W*0.32;cmd.y=s.H*0.44;workers=[];var N=9;
1141
+ for(var i=0;i<N;i++){var a=(i/N)*6.28;workers.push({a:a,r:s.W*0.16+Math.random()*s.W*0.14,w:.0015+Math.random()*.0025,ph:Math.random()*6.28,x:0,y:0});}}
1142
+ function wpos(w){var r=w.r+Math.sin(s.t*.01+w.ph)*14;return{x:cmd.x+Math.cos(w.a)*r*1.35,y:cmd.y+Math.sin(w.a)*r*.62};}
1143
+ return {resize:setup, draw:function(){
1144
+ s.dim(.3);
1145
+ var i,w,P;
1146
+ // cnet strip along the bottom
1147
+ var ry=s.H*0.88;
1148
+ ctx.strokeStyle=rgba(C.C2,.10);ctx.lineWidth=1;ctx.beginPath();ctx.moveTo(s.W*0.06,ry);ctx.lineTo(s.W*0.94,ry);ctx.stroke();
1149
+ for(i=ticks.length-1;i>=0;i--){var tk=ticks[i];tk.x+=.55;tk.a*=.997;if(tk.x>s.W*0.94||tk.a<.03){ticks.splice(i,1);continue;}
1150
+ ctx.fillStyle=rgba(tk.hue,tk.a);ctx.fillRect(tk.x,ry-5,2,10);}
1151
+ // workers in formation
1152
+ for(i=0;i<workers.length;i++){w=workers[i];w.a+=w.w;P=wpos(w);w.x=P.x;w.y=P.y;
1153
+ var pl=.5+.5*Math.sin(s.t*.06+w.ph);
1154
+ ctx.fillStyle=rgba(C.C2,.5+.3*pl);ctx.beginPath();ctx.arc(P.x,P.y,2.1,0,7);ctx.fill();
1155
+ ctx.fillStyle=rgba(C.C2,.08);ctx.beginPath();ctx.arc(P.x,P.y,7,0,7);ctx.fill();
1156
+ // occasionally a worker emits an event pulse toward the commander
1157
+ if(Math.random()<.0035&&events.length<4)events.push({from:i,p:0});}
1158
+ // event pulses inbound
1159
+ for(i=events.length-1;i>=0;i--){var ev=events[i];ev.p+=.02;w=workers[ev.from];
1160
+ var ex=w.x+(cmd.x-w.x)*ev.p,ey=w.y+(cmd.y-w.y)*ev.p;
1161
+ if(ev.p>=1){events.splice(i,1);wake=1;
1162
+ beams.push({to:ev.from,p:0,hue:Math.random()<.25?C.MG:C.AC});
1163
+ ticks.push({x:s.W*0.06,a:.8,hue:C.C2});continue;}
1164
+ ctx.fillStyle=rgba(C.MG,.75*(1-ev.p*.4));ctx.beginPath();ctx.arc(ex,ey,1.8,0,7);ctx.fill();
1165
+ ctx.strokeStyle=rgba(C.MG,.16*(1-ev.p));ctx.lineWidth=1;ctx.beginPath();ctx.moveTo(w.x,w.y);ctx.lineTo(ex,ey);ctx.stroke();}
1166
+ // verb beams outbound
1167
+ for(i=beams.length-1;i>=0;i--){var b=beams[i];b.p+=.028;w=workers[b.to];
1168
+ var bx=cmd.x+(w.x-cmd.x)*b.p,by=cmd.y+(w.y-cmd.y)*b.p;
1169
+ if(b.p>=1){beams.splice(i,1);ticks.push({x:s.W*0.06,a:.9,hue:b.hue});continue;}
1170
+ ctx.strokeStyle=rgba(b.hue,.4*(1-b.p*.5));ctx.lineWidth=1.4;ctx.beginPath();ctx.moveTo(cmd.x,cmd.y);ctx.lineTo(bx,by);ctx.stroke();
1171
+ ctx.fillStyle=rgba(b.hue,.9);ctx.beginPath();ctx.arc(bx,by,2.2,0,7);ctx.fill();}
1172
+ // commander: current-projection ring + operator-turn flare
1173
+ wake*=.94;
1174
+ ctx.save();ctx.translate(cmd.x,cmd.y);ctx.rotate(s.t*.003);
1175
+ ctx.setLineDash([3,12]);ctx.strokeStyle=rgba(C.AC,.22+.4*wake);ctx.lineWidth=1;ctx.beginPath();ctx.arc(0,0,34,0,7);ctx.stroke();ctx.setLineDash([]);ctx.restore();
1176
+ var gg=ctx.createRadialGradient(cmd.x,cmd.y,0,cmd.x,cmd.y,70);gg.addColorStop(0,rgba(C.AC,.4+.4*wake));gg.addColorStop(.5,rgba(C.AC,.1));gg.addColorStop(1,rgba(C.AC,0));ctx.fillStyle=gg;ctx.fillRect(cmd.x-70,cmd.y-70,140,140);
1177
+ // commander body: a quad glyph
1178
+ ctx.strokeStyle=rgba(C.AC,.9);ctx.lineWidth=1.4;
1179
+ ctx.beginPath();ctx.moveTo(cmd.x-7,cmd.y-7);ctx.lineTo(cmd.x+7,cmd.y+7);ctx.moveTo(cmd.x+7,cmd.y-7);ctx.lineTo(cmd.x-7,cmd.y+7);ctx.stroke();
1180
+ for(var q=0;q<4;q++){var qa=q*1.5708+.7854;ctx.beginPath();ctx.arc(cmd.x+Math.cos(qa)*9.9,cmd.y+Math.sin(qa)*9.9,2.6+wake*1.6,0,7);ctx.stroke();}
1181
+ }};
1182
+ }
1183
+
1184
+ // =========================================================
1185
+ // SCENE — HANGAR: SEALED BAYS
1186
+ // A grid of sealed bays, one drone patrolling inside each.
1187
+ // Walls flare when their drone approaches — nothing crosses.
1188
+ // Periodically a bay door opens, its drone launches out along
1189
+ // a lane (a sortie), the bay seals and respawns.
1190
+ // =========================================================
1191
+ function buildHangar(s,ctx,rgba,C){
1192
+ var bays=[],CO=3,RO=2;
1193
+ function setup(){bays=[];var gw=s.W*0.72,gh=s.H*0.62,x0=s.W*0.14,y0=s.H*0.16;
1194
+ for(var r=0;r<RO;r++)for(var c=0;c<CO;c++){var bw=gw/CO-18,bh=gh/RO-18;
1195
+ bays.push({x:x0+c*(gw/CO),y:y0+r*(gh/RO),w:bw,h:bh,dx:Math.random()*bw,dy:Math.random()*bh,vx:.6+Math.random()*.7,vy:.4+Math.random()*.6,flare:0,door:0,out:null,respawn:0});}}
1196
+ return {resize:setup, draw:function(){
1197
+ s.dim(.32);
1198
+ for(var i=0;i<bays.length;i++){var b=bays[i];
1199
+ // launch cycle: occasionally open the door and fly out
1200
+ if(!b.out&&b.respawn<=0&&Math.random()<.0009)b.out={p:0};
1201
+ if(b.respawn>0)b.respawn--;
1202
+ var doorT=b.out?Math.min(1,b.door+=.04):(b.door=Math.max(0,b.door-.05));
1203
+ // walls (door = gap in the right wall when open)
1204
+ b.flare*=.9;
1205
+ ctx.strokeStyle=rgba(C.AC,.14+.5*b.flare);ctx.lineWidth=1;
1206
+ ctx.beginPath();ctx.moveTo(b.x,b.y);ctx.lineTo(b.x+b.w,b.y);ctx.stroke();
1207
+ ctx.beginPath();ctx.moveTo(b.x,b.y+b.h);ctx.lineTo(b.x+b.w,b.y+b.h);ctx.stroke();
1208
+ ctx.beginPath();ctx.moveTo(b.x,b.y);ctx.lineTo(b.x,b.y+b.h);ctx.stroke();
1209
+ var gap=b.h*.5*doorT;
1210
+ ctx.beginPath();ctx.moveTo(b.x+b.w,b.y);ctx.lineTo(b.x+b.w,b.y+b.h/2-gap/2);ctx.moveTo(b.x+b.w,b.y+b.h/2+gap/2);ctx.lineTo(b.x+b.w,b.y+b.h);ctx.stroke();
1211
+ // corner rivets
1212
+ ctx.fillStyle=rgba(C.AC,.35);[[b.x,b.y],[b.x+b.w,b.y],[b.x,b.y+b.h],[b.x+b.w,b.y+b.h]].forEach(function(pt){ctx.fillRect(pt[0]-1.5,pt[1]-1.5,3,3);});
1213
+ if(b.out){ // sortie: fly out through the door and off-canvas
1214
+ b.out.p+=.012;var sx=b.x+b.dx,sy=b.y+b.dy,tx=s.W+40,ty=b.y+b.h/2;
1215
+ var ox=sx+(tx-sx)*b.out.p,oy=sy+(ty-sy)*Math.min(1,b.out.p*1.4);
1216
+ ctx.fillStyle=rgba(C.AC,.9);ctx.beginPath();ctx.arc(ox,oy,2.4,0,7);ctx.fill();
1217
+ ctx.strokeStyle=rgba(C.AC,.25);ctx.beginPath();ctx.moveTo(ox-16,oy);ctx.lineTo(ox,oy);ctx.stroke();
1218
+ if(b.out.p>=1){b.out=null;b.respawn=240;b.dx=b.w*.5;b.dy=b.h*.5;}
1219
+ continue;}
1220
+ if(b.respawn>0)continue; // bay resealing, empty
1221
+ // patrol inside; bounce off walls, flare them
1222
+ b.dx+=b.vx;b.dy+=b.vy;
1223
+ if(b.dx<8||b.dx>b.w-8){b.vx*=-1;b.flare=1;}
1224
+ if(b.dy<8||b.dy>b.h-8){b.vy*=-1;b.flare=1;}
1225
+ var px=b.x+Math.max(4,Math.min(b.w-4,b.dx)),py=b.y+Math.max(4,Math.min(b.h-4,b.dy));
1226
+ ctx.fillStyle=rgba(C.C2,.75);ctx.beginPath();ctx.arc(px,py,2,0,7);ctx.fill();
1227
+ ctx.fillStyle=rgba(C.C2,.09);ctx.beginPath();ctx.arc(px,py,8,0,7);ctx.fill();}
1228
+ }};
1229
+ }
1230
+
1231
+ // =========================================================
1232
+ // SCENE — STRIKELOOP: RECON -> SORTIE -> ASSESS
1233
+ // A drone flies a holding ellipse left of a gate arch. Each
1234
+ // pass it fires a push-packet at the gate; early passes the
1235
+ // gate flashes a finding (magenta) back; final pass clears
1236
+ // green and the drone exits THROUGH the gate in a hard line.
1237
+ // =========================================================
1238
+ function buildStrikeloop(s,ctx,rgba,C){
1239
+ var orbit={a:0},pass=0,packet=null,verdict=0,exit=null,gx,gy,gh2;
1240
+ function setup(){gx=s.W*0.72;gy=s.H*0.5;gh2=s.H*0.22;pass=0;packet=null;exit=null;}
1241
+ function dpos(){var cx=s.W*0.30,cy=s.H*0.5;return{x:cx+Math.cos(orbit.a)*s.W*0.13,y:cy+Math.sin(orbit.a)*s.H*0.2};}
1242
+ return {resize:setup, draw:function(){
1243
+ s.dim(.3);
1244
+ verdict*=.95;
1245
+ // gate arch
1246
+ var cleared=pass>=3;
1247
+ var hue=verdict>0.05?(cleared?C.AC:C.MG):C.AC;
1248
+ ctx.strokeStyle=rgba(hue,.35+.5*verdict);ctx.lineWidth=1.6;
1249
+ ctx.beginPath();ctx.moveTo(gx,gy-gh2);ctx.lineTo(gx,gy+gh2);ctx.stroke();
1250
+ ctx.strokeStyle=rgba(hue,.18+.3*verdict);
1251
+ ctx.beginPath();ctx.moveTo(gx+14,gy-gh2);ctx.lineTo(gx+14,gy+gh2);ctx.stroke();
1252
+ ctx.fillStyle=rgba(hue,.5+.4*verdict);ctx.beginPath();ctx.arc(gx,gy-gh2,2.5,0,7);ctx.fill();ctx.beginPath();ctx.arc(gx,gy+gh2,2.5,0,7);ctx.fill();
1253
+ if(exit){ // cleared: hard line through the gate and off
1254
+ exit.x+=7;
1255
+ ctx.fillStyle=rgba(C.AC,.95);ctx.beginPath();ctx.arc(exit.x,gy,3,0,7);ctx.fill();
1256
+ ctx.strokeStyle=rgba(C.AC,.4);ctx.lineWidth=1.6;ctx.beginPath();ctx.moveTo(exit.x-34,gy);ctx.lineTo(exit.x,gy);ctx.stroke();
1257
+ if(exit.x>s.W+40){exit=null;pass=0;}
1258
+ return;}
1259
+ // holding pattern
1260
+ orbit.a+=.016;var D=dpos();
1261
+ ctx.strokeStyle=rgba(C.C2,.08);ctx.lineWidth=1;ctx.beginPath();
1262
+ for(var q=0;q<=40;q++){var qa=(q/40)*6.28;var qx=s.W*0.30+Math.cos(qa)*s.W*0.13,qy=s.H*0.5+Math.sin(qa)*s.H*0.2;q===0?ctx.moveTo(qx,qy):ctx.lineTo(qx,qy);}ctx.stroke();
1263
+ ctx.fillStyle=rgba(C.AC,.9);ctx.beginPath();ctx.arc(D.x,D.y,2.6,0,7);ctx.fill();
1264
+ ctx.fillStyle=rgba(C.AC,.1);ctx.beginPath();ctx.arc(D.x,D.y,9,0,7);ctx.fill();
1265
+ // fire a push-packet once per pass (when crossing rightmost point)
1266
+ if(!packet&&Math.cos(orbit.a)>0.996)packet={x:D.x,y:D.y,ret:false};
1267
+ if(packet){
1268
+ if(!packet.ret){packet.x+=5.2;packet.y+=(gy-packet.y)*.06;
1269
+ ctx.fillStyle=rgba(C.AC,.85);ctx.beginPath();ctx.arc(packet.x,packet.y,2,0,7);ctx.fill();
1270
+ if(packet.x>=gx){pass++;verdict=1;
1271
+ if(pass>=3){packet=null;exit={x:gx};}
1272
+ else packet.ret=true;}}
1273
+ else{packet.x-=4.4;packet.y+=(D.y-packet.y)*.08; // finding flies back
1274
+ ctx.fillStyle=rgba(C.MG,.85);ctx.beginPath();ctx.arc(packet.x,packet.y,2,0,7);ctx.fill();
1275
+ ctx.strokeStyle=rgba(C.MG,.15);ctx.beginPath();ctx.moveTo(packet.x,packet.y);ctx.lineTo(packet.x+14,packet.y);ctx.stroke();
1276
+ if(packet.x<=D.x+8)packet=null;}}
1277
+ }};
1278
+ }
1279
+
1280
+ // =========================================================
1281
+ // SCENE — FORGE: SOURCE -> ONE BINARY -> HARDLINKED WING
1282
+ // Source motes (cyan) get drawn into a forge point; it pulses
1283
+ // and a bright bar (the binary) charges; at full charge it
1284
+ // stamps a wing of hardlinked drones that lift off rightward.
1285
+ // =========================================================
1286
+ function buildForge(s,ctx,rgba,C){
1287
+ var motes=[],charge=0,fleet=[],fx,fy;
1288
+ function setup(){fx=s.W*0.38;fy=s.H*0.5;motes=[];for(var i=0;i<70;i++)motes.push(mk());fleet=[];charge=0;}
1289
+ function mk(){var a=Math.random()*6.28,r=s.W*(0.18+Math.random()*0.22);return{x:fx+Math.cos(a)*r,y:fy+Math.sin(a)*r*.6,sp:.006+Math.random()*.012};}
1290
+ return {resize:setup, draw:function(){
1291
+ s.dim(.3);
1292
+ // source motes spiral into the forge
1293
+ for(var i=0;i<motes.length;i++){var m=motes[i];
1294
+ m.x+=(fx-m.x)*m.sp;m.y+=(fy-m.y)*m.sp;
1295
+ var d=Math.hypot(m.x-fx,m.y-fy);
1296
+ if(d<6){charge=Math.min(1,charge+.012);motes[i]=mk();continue;}
1297
+ ctx.fillStyle=rgba(C.C2,.5);ctx.beginPath();ctx.arc(m.x,m.y,1.3,0,7);ctx.fill();}
1298
+ // the binary bar charging
1299
+ var bw=90,bh=10;
1300
+ ctx.strokeStyle=rgba(C.AC,.4);ctx.lineWidth=1;ctx.strokeRect(fx-bw/2,fy-bh/2,bw,bh);
1301
+ ctx.fillStyle=rgba(C.AC,.25+.55*charge);ctx.fillRect(fx-bw/2+1,fy-bh/2+1,(bw-2)*charge,bh-2);
1302
+ var gg=ctx.createRadialGradient(fx,fy,0,fx,fy,80+60*charge);gg.addColorStop(0,rgba(C.AC,.25+.35*charge));gg.addColorStop(1,rgba(C.AC,0));ctx.fillStyle=gg;ctx.fillRect(fx-140,fy-140,280,280);
1303
+ // at full charge: stamp a wing of hardlinks
1304
+ if(charge>=1){charge=0;for(var k=0;k<5;k++)fleet.push({x:fx+bw/2,y:fy,ty:s.H*(0.2+0.15*k),p:0});}
1305
+ for(var f=fleet.length-1;f>=0;f--){var fl=fleet[f];fl.p+=.008;
1306
+ fl.x+=3.2;fl.y+=(fl.ty-fl.y)*.04;
1307
+ if(fl.x>s.W+30){fleet.splice(f,1);continue;}
1308
+ ctx.fillStyle=rgba(C.AC,.85);ctx.beginPath();ctx.arc(fl.x,fl.y,2.2,0,7);ctx.fill();
1309
+ ctx.strokeStyle=rgba(C.AC,.2);ctx.lineWidth=1;ctx.beginPath();ctx.moveTo(fl.x-18,fl.y);ctx.lineTo(fl.x,fl.y);ctx.stroke();}
1310
+ }};
1311
+ }
1312
+
1313
+ // =========================================================
1314
+ // SCENE — NIGHTSHIFT: moon crosses; sorties arc up off the
1315
+ // horizon and land back as green merge checkmarks by morning.
1316
+ // =========================================================
1317
+ function buildNightshift(s,ctx,rgba,C){
1318
+ var t=0,sorties=[],stamps=[];
1319
+ function setup(){t=0;sorties=[];stamps=[];}
1320
+ return {resize:setup, draw:function(){
1321
+ s.dim(.3);t+=.0022;var prog=t%1;
1322
+ var hy=s.H*0.72;
1323
+ ctx.strokeStyle=rgba(C.C2,.14);ctx.lineWidth=1;ctx.beginPath();ctx.moveTo(s.W*0.08,hy);ctx.lineTo(s.W*0.92,hy);ctx.stroke();
1324
+ var ma=Math.PI*(1-prog),mx=s.W*0.5+Math.cos(ma)*s.W*0.38,my=hy-Math.sin(ma)*s.H*0.42;
1325
+ ctx.fillStyle=rgba(C.C2,.65);ctx.beginPath();ctx.arc(mx,my,3.6,0,7);ctx.fill();
1326
+ ctx.fillStyle=rgba(C.C2,.07);ctx.beginPath();ctx.arc(mx,my,14,0,7);ctx.fill();
1327
+ if(Math.random()<.018&&sorties.length<6)sorties.push({x:s.W*(0.14+Math.random()*0.64),y:hy,vy:-(1.5+Math.random()*.8),age:0});
1328
+ for(var i=sorties.length-1;i>=0;i--){var o=sorties[i];o.age+=.01;o.y+=o.vy;o.vy+=.017;
1329
+ if(o.y>=hy&&o.age>.25){sorties.splice(i,1);stamps.push({x:o.x,a:1});if(stamps.length>14)stamps.shift();continue;}
1330
+ ctx.fillStyle=rgba(C.AC,.85);ctx.beginPath();ctx.arc(o.x,o.y,1.9,0,7);ctx.fill();
1331
+ ctx.strokeStyle=rgba(C.AC,.14);ctx.beginPath();ctx.moveTo(o.x,o.y);ctx.lineTo(o.x,Math.min(hy,o.y+16));ctx.stroke();}
1332
+ for(var k=0;k<stamps.length;k++){var st=stamps[k];st.a=Math.max(.35,st.a*.997);
1333
+ ctx.strokeStyle=rgba(C.AC,.2+.6*st.a);ctx.lineWidth=1.6;
1334
+ ctx.beginPath();ctx.moveTo(st.x-4,hy+9);ctx.lineTo(st.x-1,hy+13);ctx.lineTo(st.x+5,hy+4);ctx.stroke();}
1335
+ }};
1336
+ }
1337
+
1338
+ // =========================================================
1339
+ // SCENE — PLANSPLIT: a PRD block feeds a dependency DAG;
1340
+ // ready nodes complete in waves, unlocking their children.
1341
+ // =========================================================
1342
+ function buildPlansplit(s,ctx,rgba,C){
1343
+ var nodes=[],links=[],t=0,hold=0;
1344
+ function setup(){nodes=[];links=[];t=0;hold=0;
1345
+ for(var c=0;c<4;c++){var n=2+((c*5+1)%3);
1346
+ for(var r=0;r<n;r++)nodes.push({c:c,i:r,n:n,done:0});}
1347
+ for(var k=0;k<nodes.length;k++){var b=nodes[k];if(b.c===0)continue;
1348
+ var prevs=nodes.filter(function(m){return m.c===b.c-1;});
1349
+ links.push({a:prevs[k%prevs.length],b:b});}}
1350
+ function pos(nd){return{x:s.W*(0.24+nd.c*0.19),y:s.H*(0.5+(nd.i-(nd.n-1)/2)*0.19)};}
1351
+ return {resize:setup, draw:function(){
1352
+ s.dim(.3);t+=.016;
1353
+ var px=s.W*0.08,py=s.H*0.5;
1354
+ ctx.strokeStyle=rgba(C.C2,.4);ctx.lineWidth=1.2;ctx.strokeRect(px-13,py-19,26,38);
1355
+ ctx.fillStyle=rgba(C.C2,.18);for(var q=0;q<4;q++)ctx.fillRect(px-8,py-13+q*9,16,2);
1356
+ if(nodes.every(function(n){return n.done>=1;})){hold+=.016;if(hold>1.6){nodes.forEach(function(n){n.done=0;});hold=0;}}
1357
+ nodes.forEach(function(nd){
1358
+ var parents=links.filter(function(l){return l.b===nd;}).map(function(l){return l.a;});
1359
+ var gate=parents.length?Math.min.apply(null,parents.map(function(p){return p.done;})):1;
1360
+ if(gate>=1)nd.done=Math.min(1,nd.done+.005);});
1361
+ ctx.strokeStyle=rgba(C.C2,.25);ctx.beginPath();ctx.moveTo(px+13,py);ctx.lineTo(pos(nodes[0]).x-7,pos(nodes[0]).y);ctx.stroke();
1362
+ links.forEach(function(l){var A=pos(l.a),B=pos(l.b);
1363
+ ctx.strokeStyle=rgba(l.a.done>=1?C.AC:C.C2,l.a.done>=1?.3:.1);ctx.lineWidth=1;
1364
+ ctx.beginPath();ctx.moveTo(A.x,A.y);ctx.lineTo(B.x,B.y);ctx.stroke();});
1365
+ nodes.forEach(function(nd){var P=pos(nd);var active=nd.done>0&&nd.done<1;
1366
+ ctx.fillStyle=rgba(nd.done>0?C.AC:C.C2,nd.done>=1?.85:(active?.7:.3));
1367
+ ctx.beginPath();ctx.arc(P.x,P.y,active?3.1:2.3,0,7);ctx.fill();
1368
+ if(active){ctx.strokeStyle=rgba(C.AC,.25);ctx.beginPath();ctx.arc(P.x,P.y,6.5+2.5*Math.sin(t*5),0,7);ctx.stroke();}});
1369
+ }};
1370
+ }
1371
+
1372
+ // =========================================================
1373
+ // SCENE — SCRAMBLE: one binary block hardlinks parts into a
1374
+ // bay in fast bursts; the armed drone lifts off; next bay.
1375
+ // =========================================================
1376
+ function buildScramble(s,ctx,rgba,C){
1377
+ var t=0,slot=0,fill=0,links=[],lift=null;
1378
+ function setup(){t=0;slot=0;fill=0;links=[];lift=null;}
1379
+ return {resize:setup, draw:function(){
1380
+ s.dim(.3);t+=.016;
1381
+ var bx=s.W*0.22,by=s.H*0.5;
1382
+ ctx.strokeStyle=rgba(C.AC,.5);ctx.lineWidth=1.4;ctx.strokeRect(bx-32,by-11,64,22);
1383
+ ctx.fillStyle=rgba(C.AC,.28);ctx.fillRect(bx-30,by-9,60,18);
1384
+ var gx=s.W*(0.62),gy=s.H*(0.3+0.2*slot);
1385
+ if(!lift){
1386
+ if(Math.random()<.3&&links.length<3&&fill<5)links.push({p:0,tx:gx-18+9*fill,ty:gy});
1387
+ for(var i=links.length-1;i>=0;i--){var L=links[i];L.p+=.09;
1388
+ var lx=bx+32+(L.tx-bx-32)*L.p,ly=by+(L.ty-by)*L.p;
1389
+ ctx.strokeStyle=rgba(C.C2,.5*(1-L.p)+.1);ctx.lineWidth=1;
1390
+ ctx.beginPath();ctx.moveTo(bx+32,by);ctx.lineTo(lx,ly);ctx.stroke();
1391
+ ctx.fillStyle=rgba(C.C2,.8);ctx.beginPath();ctx.arc(lx,ly,1.8,0,7);ctx.fill();
1392
+ if(L.p>=1){links.splice(i,1);fill++;}}
1393
+ for(var f=0;f<fill;f++){ctx.fillStyle=rgba(C.AC,.7);ctx.fillRect(gx-20+9*f,gy-3,6,6);}
1394
+ ctx.strokeStyle=rgba(C.C2,.25);ctx.strokeRect(gx-24,gy-8,52,16);
1395
+ if(fill>=5){lift={x:gx,y:gy};fill=0;links=[];}
1396
+ } else {
1397
+ lift.x+=4.2;lift.y-=1.3;
1398
+ ctx.fillStyle=rgba(C.AC,.9);ctx.beginPath();ctx.arc(lift.x,lift.y,2.4,0,7);ctx.fill();
1399
+ ctx.strokeStyle=rgba(C.AC,.25);ctx.beginPath();ctx.moveTo(lift.x-20,lift.y+7);ctx.lineTo(lift.x,lift.y);ctx.stroke();
1400
+ if(lift.x>s.W+30){lift=null;slot=(slot+1)%3;}}
1401
+ }};
1402
+ }
1403
+
1404
+ // =========================================================
1405
+ // SCENE — CIPHER: host -> relay -> guest. Packets leave the
1406
+ // host green, turn to magenta noise inside the relay band,
1407
+ // and clear green again at the guest. Keys stay at the ends.
1408
+ // =========================================================
1409
+ function buildCipher(s,ctx,rgba,C){
1410
+ var pk=[],t=0;
1411
+ function setup(){pk=[];t=0;}
1412
+ function key(x,y){ctx.strokeStyle=rgba(C.AC,.7);ctx.lineWidth=1.4;
1413
+ ctx.beginPath();ctx.arc(x,y,3.4,0,7);ctx.stroke();
1414
+ ctx.beginPath();ctx.moveTo(x+3.4,y);ctx.lineTo(x+11,y);ctx.moveTo(x+8,y);ctx.lineTo(x+8,y+4);ctx.stroke();}
1415
+ return {resize:setup, draw:function(){
1416
+ s.dim(.3);t+=.016;
1417
+ var hx=s.W*0.16,gx2=s.W*0.84,cy=s.H*0.5,r1=s.W*0.42,r2=s.W*0.58;
1418
+ ctx.strokeStyle=rgba(C.MG,.28);ctx.lineWidth=1;
1419
+ ctx.strokeRect(r1,cy-s.H*0.26,r2-r1,s.H*0.52);
1420
+ ctx.fillStyle=rgba(C.MG,.05);ctx.fillRect(r1,cy-s.H*0.26,r2-r1,s.H*0.52);
1421
+ ctx.strokeStyle=rgba(C.C2,.4);ctx.strokeRect(hx-16,cy-22,32,44);
1422
+ ctx.strokeRect(gx2-16,cy-22,32,44);
1423
+ key(hx-4,cy+34);key(gx2-4,cy+34);
1424
+ if(Math.random()<.06)pk.push({x:hx+16,y:cy-14+Math.random()*28,v:2.2+Math.random()});
1425
+ if(Math.random()<.03)pk.push({x:gx2-16,y:cy-14+Math.random()*28,v:-(2.2+Math.random())});
1426
+ for(var i=pk.length-1;i>=0;i--){var p=pk[i];p.x+=p.v;
1427
+ if(p.x>s.W||p.x<0){pk.splice(i,1);continue;}
1428
+ var inRelay=p.x>r1&&p.x<r2;
1429
+ if(inRelay){ctx.fillStyle=rgba(C.MG,.7);
1430
+ ctx.fillRect(p.x-2+(Math.random()*2-1),p.y-2+(Math.random()*2-1),4,4);}
1431
+ else{ctx.fillStyle=rgba(C.AC,.8);ctx.beginPath();ctx.arc(p.x,p.y,1.9,0,7);ctx.fill();}}
1432
+ }};
1433
+ }
1434
+
1435
+ // =========================================================
1436
+ // SCENE — ARSENAL: three racks of slots; a scanline sweeps
1437
+ // and arms each slot green; racks cycle like an ordnance check.
1438
+ // =========================================================
1439
+ function buildArsenal(s,ctx,rgba,C){
1440
+ var t=0;
1441
+ function setup(){t=0;}
1442
+ return {resize:setup, draw:function(){
1443
+ s.dim(.3);t+=.016;
1444
+ var racks=3,slots=6,sweep=(t*.35)%(racks+0.6);
1445
+ for(var r=0;r<racks;r++){
1446
+ var rx=s.W*(0.25+r*0.25),top=s.H*0.24,bh=s.H*0.52;
1447
+ ctx.strokeStyle=rgba(C.C2,.25);ctx.lineWidth=1;
1448
+ ctx.strokeRect(rx-30,top,60,bh);
1449
+ for(var k=0;k<slots;k++){var sy=top+bh*(k+0.5)/slots;
1450
+ var armed=sweep>r+(k/slots)*0.9;
1451
+ ctx.fillStyle=rgba(armed?C.AC:C.C2,armed?.75:.18);
1452
+ ctx.fillRect(rx-22,sy-3,44,6);
1453
+ if(armed){ctx.fillStyle=rgba(C.AC,.9);ctx.beginPath();ctx.arc(rx+28,sy,1.6,0,7);ctx.fill();}}}
1454
+ var lx=s.W*(0.25+((sweep%1))*0.5);
1455
+ ctx.strokeStyle=rgba(C.AC,.12);ctx.beginPath();ctx.moveTo(lx,s.H*0.2);ctx.lineTo(lx,s.H*0.8);ctx.stroke();
1456
+ }};
1457
+ }
1458
+
1459
+ // =========================================================
1460
+ // SCENE — IRCNET: callsigns on a ring; DMs travel chords in
1461
+ // both directions; broadcasts pulse to everyone; a parked
1462
+ // (dim) node that receives a packet lights up — revived.
1463
+ // =========================================================
1464
+ function buildIrcnet(s,ctx,rgba,C){
1465
+ var N=7,peers=[],pk=[],pulse=null,t=0;
1466
+ function setup(){t=0;pk=[];pulse=null;peers=[];
1467
+ for(var i=0;i<N;i++)peers.push({a:(i/N)*6.28-1.57,parked:i===4,glow:0});}
1468
+ function pos(p){return{x:s.W*0.5+Math.cos(p.a)*s.W*0.26,y:s.H*0.5+Math.sin(p.a)*s.H*0.3};}
1469
+ return {resize:setup, draw:function(){
1470
+ s.dim(.3);t+=.016;
1471
+ // ring
1472
+ ctx.strokeStyle=rgba(C.C2,.07);ctx.lineWidth=1;ctx.beginPath();
1473
+ for(var q=0;q<=48;q++){var qa=(q/48)*6.28;var qx=s.W*0.5+Math.cos(qa)*s.W*0.26,qy=s.H*0.5+Math.sin(qa)*s.H*0.3;q===0?ctx.moveTo(qx,qy):ctx.lineTo(qx,qy);}ctx.stroke();
1474
+ // peers
1475
+ peers.forEach(function(p,i){var P=pos(p);p.glow*=.94;
1476
+ var live=!p.parked||p.glow>.2;
1477
+ ctx.fillStyle=rgba(live?C.AC:C.C2,live?.5+.5*Math.min(1,p.glow+.4):.18);
1478
+ ctx.beginPath();ctx.arc(P.x,P.y,live?3:2.4,0,7);ctx.fill();
1479
+ if(p.glow>.05){ctx.strokeStyle=rgba(C.AC,.3*p.glow);ctx.beginPath();ctx.arc(P.x,P.y,8+6*(1-p.glow),0,7);ctx.stroke();}});
1480
+ // random DM chords
1481
+ if(Math.random()<.045&&pk.length<5){var a=Math.floor(Math.random()*N),b=Math.floor(Math.random()*N);
1482
+ if(a!==b)pk.push({f:a,to:b,p:0});}
1483
+ for(var i=pk.length-1;i>=0;i--){var m=pk[i];m.p+=.03;
1484
+ var A=pos(peers[m.f]),B=pos(peers[m.to]);
1485
+ var mx=A.x+(B.x-A.x)*m.p,my=A.y+(B.y-A.y)*m.p;
1486
+ ctx.strokeStyle=rgba(C.C2,.10);ctx.beginPath();ctx.moveTo(A.x,A.y);ctx.lineTo(B.x,B.y);ctx.stroke();
1487
+ ctx.fillStyle=rgba(C.AC,.85);ctx.beginPath();ctx.arc(mx,my,1.8,0,7);ctx.fill();
1488
+ if(m.p>=1){peers[m.to].glow=1;if(peers[m.to].parked&&Math.random()<.5)peers[m.to].parked=false;pk.splice(i,1);}}
1489
+ // occasional broadcast pulse
1490
+ if(!pulse&&Math.random()<.006)pulse={src:Math.floor(Math.random()*N),r:0};
1491
+ if(pulse){pulse.r+=4.5;var S=pos(peers[pulse.src]);
1492
+ ctx.strokeStyle=rgba(C.AC,Math.max(0,.4-pulse.r/(s.W*0.9)));ctx.lineWidth=1.4;
1493
+ ctx.beginPath();ctx.arc(S.x,S.y,pulse.r,0,7);ctx.stroke();
1494
+ peers.forEach(function(p){var P=pos(p);if(Math.abs(Math.hypot(P.x-S.x,P.y-S.y)-pulse.r)<5)p.glow=1;});
1495
+ if(pulse.r>s.W*0.6)pulse=null;}
1496
+ // re-park one node occasionally so the revive story loops
1497
+ if(Math.random()<.002){var idx=1+Math.floor(Math.random()*(N-1));peers[idx].parked=true;}
1498
+ }};
1499
+ }
1500
+
1501
+ var BUILDERS={dispatch:buildDispatch,cnet:buildCNet,harness:buildHarness,lanes:buildLanes,engine:buildEngine,converge:buildConverge,relay:buildRelay,gatewall:buildGatewall,heal:buildHeal,ouro:buildOuro,triad:buildTriad,carrier:buildCarrier,swap:buildSwap,surfaces:buildSurfaces,journal:buildJournal,swarmer:buildSwarmer,hangar:buildHangar,strikeloop:buildStrikeloop,forge:buildForge,nightshift:buildNightshift,plansplit:buildPlansplit,scramble:buildScramble,cipher:buildCipher,arsenal:buildArsenal,ircnet:buildIrcnet};
1502
+
1503
+ // ---- wire up: build a Scene per canvas, run only when on-screen ----
1504
+ var scenes=[];
1505
+ document.querySelectorAll('canvas.scene').forEach(function(cv){
1506
+ var kind=cv.getAttribute('data-scene');var b=BUILDERS[kind];if(!b)return;
1507
+ var sc=Scene(cv,b);scenes.push({cv:cv,sc:sc});
1508
+ });
1509
+ // start every scene immediately (lightweight 2D); IO only pauses fully-offscreen ones for perf
1510
+ scenes.forEach(function(s){s.sc.start();});
1511
+ if('IntersectionObserver' in window){
1512
+ var io=new IntersectionObserver(function(es){es.forEach(function(e){var s=scenes.find(function(x){return x.cv===e.target;});if(!s)return;if(e.isIntersecting)s.sc.start();else s.sc.stop();});},{rootMargin:'200px',threshold:0});
1513
+ scenes.forEach(function(s){io.observe(s.cv);});
1514
+ }
1515
+ })();
1516
+
1517
+ /* ===== HERO ASCII DRONE FLEET (grid compositor — lead drone + a squad) =====
1518
+ One blank char-grid per frame; each drone is STAMPED at an offset with its
1519
+ own bob/drift phase, then the whole grid is colorized. The !!! (rotor) and ?
1520
+ (eye) tokens survive stamping as literal chars, so a single per-line token
1521
+ pass animates every drone. Minis use dimmer glyphs (%,=,(,)) -> depth. */
1522
+ (function(){
1523
+ var el=document.getElementById('cine');if(!el)return;
1524
+ el.classList.add('mascot');
1525
+ // lead drone — bright body ($ @), big quad-rotor frame
1526
+ var ART=[" .-=-. .-=-. ", " ( !!! ) ( !!! ) ", " '-=-'._ _.'-=-' ", " '._ _.' ", " '=$$$$$$$=.' ", " =$$$$$$$$$$$= ", " $$$@@@@@@@@@@$$$ ", " $$$@@ @@$$$ ", " $$@ ? @$$$ ", " $$$@ '-' @$$$ ", " $$$@@ @@$$$ ", " $$$@@@@@@@@@@$$$ ", " =$$$$$$$$$$$= ", " '=$$$$$$$=.' ", " _.' '._ ", " .-=-.' '.-=-. ", " ( !!! ) ( !!! ) ", " '-=-' '-=-' "];
1527
+ // companion drone — dim body (% =), compact quad
1528
+ var MINI=["(!!!) (!!!)", " \\%==%/ ", " %%?%% ", " /%==%\\ ", "(!!!) (!!!)"];
1529
+ var BL=['---','\\\\\\','|||','///'];
1530
+ var GW=52,GH=27;
1531
+ // fleet: lead centered, four companions clustered tight at its corners
1532
+ var FLEET=[
1533
+ {s:MINI,x:0, y:2, amp:1,sp:0.050,ph:0.0, dx:1,dsp:0.020},
1534
+ {s:MINI,x:39, y:2, amp:1,sp:0.044,ph:2.1, dx:1,dsp:0.017},
1535
+ {s:MINI,x:1, y:20, amp:1,sp:0.048,ph:4.2, dx:1,dsp:0.023},
1536
+ {s:MINI,x:38, y:20, amp:1,sp:0.041,ph:1.3, dx:1,dsp:0.015},
1537
+ {s:ART, x:10, y:5, amp:2,sp:0.040,ph:0.0, dx:0,dsp:0.011}
1538
+ ];
1539
+ var t=0,phase=0;
1540
+ function esc(c){return c==='&'?'&amp;':c==='<'?'&lt;':c==='>'?'&gt;':c;}
1541
+ function cls(ch){
1542
+ if(ch==='$'||ch==='@')return 'd';
1543
+ if(ch==='='||ch==='%')return 'm';
1544
+ if(ch==='\\'||ch==='/')return 'r';
1545
+ if(ch==='('||ch===')'||ch==='-'||ch==='|'||ch==="'"||ch==='.')return 'c';
1546
+ return 'b';
1547
+ }
1548
+ function paint(s){var html='',run='',rc='';for(var j=0;j<s.length;j++){var ch=s[j],c=cls(ch);if(c===rc)run+=esc(ch);else{if(run)html+='<span class="g-'+rc+'">'+run+'</span>';run=esc(ch);rc=c;}}if(run)html+='<span class="g-'+rc+'">'+run+'</span>';return html;}
1549
+ function frame(){
1550
+ t++; if(t%4===0)phase++;
1551
+ var blade=BL[phase%4];
1552
+ var p=Math.sin(t*0.07);
1553
+ var core=p>0.45?'@':(p>-0.1?'o':'.');
1554
+ // blank grid
1555
+ var grid=[];for(var r=0;r<GH;r++){var row=[];for(var c=0;c<GW;c++)row.push(' ');grid.push(row);}
1556
+ // stamp each drone (companions first, lead last so it sits in front)
1557
+ for(var f=0;f<FLEET.length;f++){var d=FLEET[f];
1558
+ var oy=d.y+Math.round(d.amp*Math.sin(t*d.sp+d.ph));
1559
+ var ox=d.x+Math.round(d.dx*Math.sin(t*d.dsp+d.ph));
1560
+ for(var i=0;i<d.s.length;i++){var ln=d.s[i],ry=oy+i;if(ry<0||ry>=GH)continue;
1561
+ for(var j=0;j<ln.length;j++){var ch=ln[j];if(ch===' ')continue;var rx=ox+j;if(rx<0||rx>=GW)continue;grid[ry][rx]=ch;}}
1562
+ }
1563
+ // colorize grid line by line (same token pass as the single-drone build)
1564
+ var out=[];
1565
+ for(var r=0;r<GH;r++){
1566
+ var line=grid[r].join('');var html='';var k=0;
1567
+ while(k<line.length){
1568
+ if(line.substr(k,3)==='!!!'){ html+='<span class="g-rb">'+blade+'</span>'; k+=3; }
1569
+ else if(line[k]==='?'){ html+='<span class="g-eye">'+core+'</span>'; k+=1; }
1570
+ else { var nb=line.indexOf('!!!',k); var nc=line.indexOf('?',k);
1571
+ var nxt=Math.min(nb<0?1e9:nb, nc<0?1e9:nc, line.length);
1572
+ html+=paint(line.slice(k,nxt)); k=nxt; }
1573
+ }
1574
+ out.push(html);
1575
+ }
1576
+ el.innerHTML='<span class="sprite">'+out.join('\n')+'</span>';
1577
+ requestAnimationFrame(frame);
1578
+ }
1579
+ frame();
1580
+ })();
1581
+
1582
+ /* ===== attach-mock live spinner (mirrors the real SPINNER_FRAMES braille set) ===== */
1583
+ (function(){
1584
+ var F=['⠋','⠙','⠹','⠸','⠼','⠴','⠦','⠧','⠇','⠏'];
1585
+ var s1=document.getElementById('sp1'),s2=document.getElementById('sp2');if(!s1&&!s2)return;
1586
+ var t1=document.getElementById('spt1');
1587
+ var stages=['opening OMP session','loading Rig session'];
1588
+ var i=0,st=0;
1589
+ setInterval(function(){i=(i+1)%F.length;if(s1)s1.textContent=F[i];if(s2)s2.textContent=F[(i+4)%F.length];},150);
1590
+ setInterval(function(){st=(st+1)%stages.length;if(t1)t1.textContent=stages[st];},2600);
1591
+ })();
1592
+
1593
+ /* ===== scroll reveal + pipeline cycle ===== */
1594
+ (function(){var io=new IntersectionObserver(function(es){es.forEach(function(e){if(e.isIntersecting)e.target.classList.add('in');});},{threshold:.16});document.querySelectorAll('.rv').forEach(function(el){io.observe(el);});})();
1595
+ (function(){var st=document.querySelectorAll('#pipe .stage');if(!st.length)return;var i=0;setInterval(function(){st.forEach(function(s){s.classList.remove('on');});st[i].classList.add('on');i=(i+1)%st.length;},1300);})();
1596
+
1597
+ /* ===== closing kill-line: crisp DOM text at rest, canvas shred only during the burst ===== */
1598
+ (function(){
1599
+ var cv=document.querySelector('.ripcv'); if(!cv) return;
1600
+ var host=cv.parentElement, l2=host&&host.querySelector('.l2'); if(!l2) return;
1601
+ var ctx=cv.getContext('2d'); var DPR=Math.min(devicePixelRatio||1,2);
1602
+ var pre=cv.dataset.pre||'', hot=cv.dataset.hot||'', post=cv.dataset.post||'';
1603
+ var reduce=matchMedia('(prefers-reduced-motion: reduce)').matches;
1604
+ var off=document.createElement('canvas'), octx=off.getContext('2d');
1605
+ var W=0,H=0,fs=40,preW=0,hotW=0,postW=0,x0=0,hx=0,GP=20,t=0,BANDS=7,shredding=false;
1606
+ function fontStr(){return '700 '+fs+"px 'Space Grotesk',system-ui,sans-serif";}
1607
+ function build(){
1608
+ fs=parseFloat(getComputedStyle(l2).fontSize)||40; GP=Math.round(fs*0.5);
1609
+ ctx.font=fontStr();
1610
+ preW=ctx.measureText(pre).width; hotW=ctx.measureText(hot).width; postW=ctx.measureText(post).width;
1611
+ var lineW=preW+hotW+postW, padX=Math.round(fs);
1612
+ W=Math.ceil(lineW+padX*2); H=Math.ceil(fs*1.75);
1613
+ cv.width=W*DPR; cv.height=H*DPR; cv.style.width=W+'px'; cv.style.height=H+'px';
1614
+ ctx.setTransform(DPR,0,0,DPR,0,0);
1615
+ x0=Math.round((W-lineW)/2); hx=x0+preW;
1616
+ off.width=Math.ceil((hotW+GP*2)*DPR); off.height=Math.ceil(H*DPR);
1617
+ octx.setTransform(DPR,0,0,DPR,0,0); octx.clearRect(0,0,off.width,off.height);
1618
+ octx.font=fontStr(); octx.textBaseline='middle';
1619
+ octx.shadowColor='rgba(204,255,77,.5)'; octx.shadowBlur=Math.max(8,Math.round(fs*0.2)); octx.fillStyle='#ccff4d';
1620
+ octx.fillText(hot, GP, H/2); octx.shadowBlur=0;
1621
+ }
1622
+ function ink(){ ctx.font=fontStr(); ctx.textBaseline='middle'; ctx.fillStyle='#f2f3f6'; ctx.fillText(pre,x0,H/2); ctx.fillText(post,hx+hotW,H/2); }
1623
+ function frame(){
1624
+ t++;
1625
+ var cycle=170, ph=t%cycle;
1626
+ var rip = ph<26 ? Math.pow(1-ph/26,1.7)*(ph<3?ph/3:1) : 0;
1627
+ if(rip<0.015){
1628
+ if(shredding){ shredding=false; cv.style.opacity='0'; l2.style.visibility='visible'; ctx.clearRect(0,0,W,H); }
1629
+ requestAnimationFrame(frame); return;
1630
+ }
1631
+ if(!shredding){ shredding=true; cv.style.opacity='1'; l2.style.visibility='hidden'; }
1632
+ ctx.clearRect(0,0,W,H); ink();
1633
+ var bandH=H/BANDS;
1634
+ for(var i=0;i<BANDS;i++){
1635
+ var mid=(i+0.5)/BANDS-0.5;
1636
+ var dx=(i%2?1:-1)*rip*(fs*0.12+Math.abs(mid)*fs*0.9);
1637
+ var dy=mid*rip*fs*0.42;
1638
+ ctx.drawImage(off, 0, Math.floor(i*bandH*DPR), off.width, Math.ceil(bandH*DPR), hx-GP+dx, i*bandH+dy, hotW+GP*2, bandH+0.6);
1639
+ }
1640
+ ctx.save(); ctx.globalAlpha=Math.min(0.92,rip); ctx.font=fontStr(); ctx.textBaseline='middle';
1641
+ ctx.shadowColor='rgba(204,255,77,.85)'; ctx.shadowBlur=Math.round(fs*0.35); ctx.fillStyle='#f3ffce'; ctx.fillText(hot,hx,H/2); ctx.restore();
1642
+ ctx.save(); ctx.globalAlpha=Math.min(1,rip*1.4); var ty=H/2+Math.sin(t*0.7)*2;
1643
+ var g=ctx.createLinearGradient(hx-12,0,hx+hotW+12,0);
1644
+ g.addColorStop(0,'rgba(204,255,77,0)'); g.addColorStop(.5,'rgba(255,255,255,.95)'); g.addColorStop(1,'rgba(204,255,77,0)');
1645
+ ctx.fillStyle=g; ctx.shadowColor='#ccff4d'; ctx.shadowBlur=12; ctx.fillRect(hx-12,ty-Math.max(2,fs*0.045),hotW+24,Math.max(3,fs*0.08)); ctx.restore();
1646
+ requestAnimationFrame(frame);
1647
+ }
1648
+ function start(){ build(); if(reduce)return; requestAnimationFrame(frame); }
1649
+ if(document.fonts&&document.fonts.ready) document.fonts.ready.then(start); else start();
1650
+ var rz; addEventListener('resize',function(){clearTimeout(rz);rz=setTimeout(build,150);});
1651
+ })();
1652
+
1653
+ </script><script src="assets/site.js?v=v9"></script></body></html>