diff --git a/docs/assets/apple-touch-icon.png b/docs/assets/apple-touch-icon.png new file mode 100644 index 0000000000..fc976f183e Binary files /dev/null and b/docs/assets/apple-touch-icon.png differ diff --git a/docs/assets/avatars.js b/docs/assets/avatars.js new file mode 100644 index 0000000000..e21886269f --- /dev/null +++ b/docs/assets/avatars.js @@ -0,0 +1,2 @@ +/* The Office cast and avatar presets, drawn by the app itself: src/renderer/src/scene/office/portraitArt.ts and src/shared/avatars.ts at rc/0.5.8, bundled unchanged except one added export, costumeFrames (the app's own costume art, used for the Pirate Ship example, 7 Nov). Exposes window.MDAv. */ +(()=>{var Je=["pale","light","tan","brown","dark","deep"],Qe=["oval","round","square","long","heart","heavy"],Ze=["normal","wide","dot","squint","happy","lashed"],et=["flat","angry","raised","soft","thick","arched"],tt=["small","normal","long","wide"],ot=["neutral","smile","frown","grin","open","smirk"],st=["mustache","mustacheSm","stubble","goatee"],nt=["styleShort","styleFloppy","styleFrame","styleBun","styleCurly","styleMessy","styleRecede","styleSpiky","styleBald","styleLong","styleAfro","styleBuzz","styleMohawk"],rt=["suit","dressshirt","shirt_open","polo","sweater","sweater_v","tee","hoodie","vest","blazer_tee","turtleneck"],at=["blouse","cardigan","shirt_w","boatneck","dress","wrap","blazer_w","stripe_tee","hoodie","cami","turtleneck"];var it=new Set(["suit","dressshirt","vest"]),ct=new Set(["polo","cardigan","tee","blazer_tee","blazer_w","stripe_tee","sweater_v"]);function L(e){let t={...e};return t.face||(t.face=t.heavy?"heavy":"oval"),t.eyes||(t.eyes=t.lashes?"lashed":"normal"),t.brow||(t.brow="flat"),t.nose||(t.nose="normal"),t.mouth||(t.mouth="neutral"),t.heavy=t.face==="heavy",t.lashes=t.eyes==="lashed",t}function T(e){var r,a,i,c,h,l;let t=L(e),o=m=>m?m.join("."):"",s=(r=t.hairargs)!=null?r:{};return[t.skin,t.face,t.eyes,o(t.eyec),t.brow,t.nose,t.mouth,o(t.mouthc),t.blush?1:0,(a=t.facial)!=null?a:"",t.glasses?1:0,t.hair,o(t.hairc),(i=s.part)!=null?i:"",(c=s.recede)!=null?c:"",(h=s.length)!=null?h:"",(l=s.vol)!=null?l:"",t.cloth,o(t.c1),o(t.c2),o(t.tie),o(t.pants)].join("|")}var Ce={black:[24,18,22],espresso:[42,32,24],darkBrown:[64,48,28],brown:[92,60,34],chestnut:[120,76,42],auburn:[154,82,46],ginger:[196,98,44],honey:[186,154,90],blonde:[196,162,110],platinum:[224,206,170],grey:[170,166,156],white:[226,222,214],blue:[70,90,170],pink:[214,120,170],green:[70,140,100]},Ae={charcoal:[58,63,74],navy:[46,58,96],slate:[110,140,180],sky:[172,196,224],teal:[60,130,130],forest:[70,110,80],olive:[126,130,96],mustard:[184,155,62],sand:[150,120,86],rust:[176,65,58],wine:[122,60,74],plum:[150,146,170],rose:[236,174,192],magenta:[212,90,158],coral:[176,86,74],brown:[120,78,52],tan:[150,120,86],cream:[235,233,226],white:[244,242,238],black:[40,40,50]},lt=[[170,58,58],[120,130,150],[120,82,46],[40,40,50],[60,100,160],[120,78,52],[200,160,60]],ht=[[46,38,42],[60,90,140],[70,110,70],[110,70,40]],ft=[[158,86,80],[190,40,60],[120,60,70]];function mt(e){let t=e>>>0;return()=>{t+=1831565813;let o=t;return o=Math.imul(o^o>>>15,o|1),o^=o+Math.imul(o^o>>>7,o|61),((o^o>>>14)>>>0)/4294967296}}var v=(e,t)=>t[Math.floor(e()*t.length)],ie=(e,t)=>t[v(e,Object.keys(t))],dt=["black","espresso","darkBrown","brown","chestnut","auburn","ginger","honey","blonde","platinum","grey","white"];function ce(e,t){let o=mt(e),s=t!=null?t:v(o,["men","women"]),r=v(o,nt),a={};(r==="styleShort"||r==="styleRecede")&&(a.part=v(o,["L","R"]),o()<.3&&(a.recede=1)),r==="styleFrame"&&(a.length=16+Math.floor(o()*5),a.vol=Math.floor(o()*3)),r==="styleMessy"&&(a.length=9+Math.floor(o()*7)),r==="styleLong"&&(a.length=21+Math.floor(o()*5));let i=v(o,s==="men"?rt:at),c={skin:v(o,Je),hairc:o()<.9?Ce[v(o,dt)]:ie(o,Ce),hair:r,hairargs:a,cloth:i,c1:ie(o,Ae),face:v(o,Qe),eyes:s==="women"&&o()<.6?"lashed":v(o,Ze),brow:v(o,et),nose:v(o,tt),mouth:v(o,ot),blush:s==="women"?o()<.4:o()<.1,glasses:o()<.25};return ct.has(i)&&(c.c2=ie(o,Ae)),it.has(i)&&o()<.7&&(c.tie=v(o,lt)),s==="men"&&o()<.3&&(c.facial=v(o,st)),o()<.15&&(c.eyec=v(o,ht.slice(1))),s==="women"&&o()<.25&&(c.mouthc=ft[1]),Object.keys(a).length===0&&delete c.hairargs,L(c)}var yt=1e3,le=24;function Y(e){let t=Math.max(0,Math.min(le-1,Math.floor(e)));return ce(yt+t,t%2===0?"men":"women")}var xt=[{name:"michael",displayName:"Michael",shirt:"#5a6b8c",blurb:"World's best boss"},{name:"jim",displayName:"Jim",shirt:"#6fa8dc",blurb:"Salesman, prankster"},{name:"pam",displayName:"Pam",shirt:"#9caf88",blurb:"Receptionist, artist"},{name:"dwight",displayName:"Dwight",shirt:"#b89b3e",blurb:"Assistant (to the) RM"},{name:"kevin",displayName:"Kevin",shirt:"#4a7ab5",blurb:"Accounting"},{name:"angela",displayName:"Angela",shirt:"#8a86a6",blurb:"Head of accounting"},{name:"oscar",displayName:"Oscar",shirt:"#7a4b6b",blurb:"Accountant"},{name:"stanley",displayName:"Stanley",shirt:"#8c5a4b",blurb:"Sales, crossword"},{name:"phyllis",displayName:"Phyllis",shirt:"#b08bbf",blurb:"Sales"},{name:"andy",displayName:"Andy",shirt:"#6fae6f",blurb:"Cornell, a cappella"},{name:"kelly",displayName:"Kelly",shirt:"#d16ba5",blurb:"Customer service"},{name:"ryan",displayName:"Ryan",shirt:"#3a3a44",blurb:"The temp"},{name:"toby",displayName:"Toby",shirt:"#9a8c5a",blurb:"Human resources"},{name:"creed",displayName:"Creed",shirt:"#6b7a4b",blurb:"Quality assurance"},{name:"meredith",displayName:"Meredith",shirt:"#b5544a",blurb:"Supplier relations"}],zo=Object.fromEntries(xt.map(e=>[e.name,e]));var U=18,z=28;var Pe={michael:"vampire",jim:"skeleton",pam:"witch",dwight:"scarecrow",kevin:"pumpkin",angela:"cat",oscar:"scientist",stanley:"undertaker",phyllis:"fortune",andy:"pirate",kelly:"batqueen",ryan:"werewolf",toby:"mummy",creed:"reaper",meredith:"bride"},Ge=["skeleton","witch","pumpkin","cat","vampire","mummy","scientist","pirate"];function _e(e){let t=T(e),o=2166136261;for(let s=0;s>>0)%Ge.length]}var k=[34,30,42],M=[58,52,70],C=[92,54,138],N=[128,84,176],w=[60,34,94],I=[242,140,40],me=[255,176,84],de=[196,100,24],Z=[104,150,56],he=[66,104,38],g=[240,236,222],G=[196,190,172],F=[236,150,176],Se=[252,252,250],ee=[222,186,102],V=[176,138,64],gt=[184,108,50],fe=[72,94,132],te=[132,120,108],J=[172,160,146],Ee=[94,84,76],Bt=[150,156,172],W=[46,42,58],Q=[70,64,86],vt=[120,206,192],Oe=[150,150,158];function ye(e,t){var o,s;switch(t){case"skeleton":return{...e,cloth:"sweater",c1:k,c2:void 0,tie:void 0,pants:k};case"mummy":return{...e,cloth:"sweater",c1:g,c2:void 0,tie:void 0,pants:G};case"vampire":return{...e,cloth:"suit",c1:w,c2:g,tie:k,pants:k};case"undertaker":return{...e,cloth:"suit",c1:k,c2:g,tie:g,pants:k};case"witch":return{...e,cloth:"sweater",c1:C,c2:void 0,tie:void 0,pants:w};case"scarecrow":return{...e,cloth:"dressshirt",c1:gt,c2:void 0,tie:void 0,pants:fe};case"pumpkin":return{...e,cloth:"sweater",c1:I,c2:void 0,tie:void 0,pants:he};case"cat":return{...e,cloth:"turtleneck",c1:k,c2:void 0,tie:void 0,pants:k};case"scientist":return{...e,cloth:"suit",c1:g,c2:e.c1,tie:(o=e.tie)!=null?o:M,pants:(s=e.pants)!=null?s:M};case"fortune":return{...e,c1:N,c2:I,pants:w};case"pirate":return{...e,cloth:"tee",c1:g,c2:void 0,tie:void 0,pants:k};case"batqueen":return{...e,c1:C,c2:k,pants:k};case"werewolf":return{...e,cloth:"tee",c1:Ee,c2:void 0,tie:void 0,pants:fe};case"reaper":return{...e,cloth:"hoodie",c1:W,c2:Q,tie:void 0,pants:W};case"bride":return{...e,cloth:"blouse",c1:g,c2:void 0,tie:void 0,pants:G}}}function O(e,t,o,s,r){for(let a=o;a<=s;a++)e.set(a,t,r)}function _(e,t,o,s=0,r=e.w-1){for(let a=t;a<=o;a++)for(let i=s;i<=r;i++)e.clear(i,a)}function wt(e){_(e,0,4),e.set(11,0,w),e.set(12,0,w),e.set(10,0,C),O(e,1,8,10,C),e.set(8,1,N),O(e,2,7,10,C),e.set(7,2,N),e.set(10,2,w),O(e,3,6,11,C),e.set(6,3,N),e.set(11,3,w),O(e,4,5,12,I),e.set(5,4,me),e.set(12,4,de),e.set(8,4,Z),e.set(9,4,Z),O(e,5,2,15,C),e.set(2,5,N),e.set(3,5,N),e.set(14,5,w),e.set(15,5,w)}function Ct(e,t){_(e,0,2);let o=s=>{for(let[r,a,i]of s)O(e,r,a,i,k)};o([[0,4,4],[1,4,5],[2,3,6],[3,3,7]]),o([[0,13,13],[1,12,13],[2,11,14],[3,10,14]]),O(e,4,4,13,k),e.set(6,4,M),e.set(11,4,M),t||(e.set(5,2,F),e.set(5,3,F),e.set(4,3,F),e.set(12,2,F),e.set(12,3,F),e.set(13,3,F))}function At(e){let t=[70,60,72];e.set(8,12,F),e.set(4,12,t),e.set(5,13,t),e.set(13,12,t),e.set(12,13,t)}function Gt(e){let t=(o,s)=>{for(let[r,a]of o)e.alpha(r,a)===255&&e.set(r,a,s)};for(let o=4;o<=13;o++)e.alpha(o,5)===255&&e.set(o,5,g);t([[5,4],[6,4],[7,3],[8,3],[9,3],[10,2],[11,2],[12,2]],g),t([[4,6],[5,6],[12,4],[13,4],[13,3]],G);for(let o of[6,9,12])e.alpha(o,5)===255&&e.set(o,5,G);e.set(14,5,g),e.set(15,6,g),e.set(15,7,G)}var D=(e,t)=>e.scene?24:t.h-1;function St(e,t){let{T:o}=t,s=D(t,e),r=(i,c)=>e.alpha(i,c)===255&&Pt(e.rgb(i,c)),a=(i,c,h=g)=>{r(i,c)&&e.set(i,c,h)};if(t.back)for(let i=o+1;i<=s;i++)a(8,i,i%2?g:G),a(9,i,G);else{for(let i=o+1;i<=s-1;i++)a(8,i),a(9,i,G);for(let i of[2,4,6]){let c=o+i;if(!(c>s)){for(let h of[5,6,7])a(h,c);for(let h of[10,11,12])a(h,c,h===12?G:g)}}}if(t.scene)for(let i=25;i<=30;i++)a(6,i),a(11,i,G)}function Pt(e){return e[0]<80&&e[1]<76&&e[2]<90&&Math.abs(e[0]-e[1])<14}function _t(e,t){let{T:o}=t,s=t.scene?30:e.h-1;for(let r=o+1;r<=s;r+=2)for(let a=0;a>1))%5!==0&&e.set(a,r,G)}}function Et(e){return e[0]>150&&e[1]>140&&e[2]>120&&e[0]-e[2]<60}function Ot(e,t){let{T:o,back:s}=t,r=t.scene?29:e.h-1;if(s){for(let a=o;a<=r;a++){let i=a=0;f--)if(e.alpha(f,a)===255){h=f;break}c<0&&(c=5,h=12),a>24&&t.scene&&(c=5,h=12);let l=Math.max(0,c-i),m=Math.min(e.w-1,h+i);for(let f=l;f<=l+1;f++)e.set(f,a,k);for(let f=m-1;f<=m;f++)e.set(f,a,k);e.set(l+2,a,w),e.set(m-2,a,w)}}function Mt(e,t){if(t.back)return;let o=[[2,14,k],[3,15,k],[3,16,C],[4,17,C],[5,18,k],[2,15,k],[2,16,k],[3,17,k],[4,18,k],[15,14,k],[14,15,k],[14,16,C],[13,17,C],[12,18,k],[15,15,k],[15,16,k],[14,17,k],[13,18,k]];for(let[s,r,a]of o)(e.alpha(s,r)!==255||r>=17)&&e.set(s,r,a)}function Nt(e,t){let o=t.mouth==="frown"?16:15;e.set(7,o,Se),e.set(10,o,Se)}var Ht=[96,46,12],Me=[136,76,34];function E(e,t,o,s,r){s.forEach((a,i)=>{for(let c=0;cs>=t.T&&s<=D(t,e)&&e.alpha(o,s)===255;function B(e,t,o,s,r){Ne(e,t,o,s)&&e.set(o,s,r)}function Ft(e,t){_(e,0,4),E(e,0,0,[".....hkkkkkkk.....",".....hkkkkkkk.....",".....hkkkkkkk.....",".....hkkkkkkk.....",".....ggggggggg....".slice(0,18),"..hkkkkkkkkkkkkk.."],{k,h:M,g:Oe}),t||e.set(12,4,g)}function It(e,t){if(t.back)return;let o=t.T+1;E(e,6,o-1,["g....g","ggkkgg","g....g"],{g:Oe,k:M}),B(e,t,12,t.T+3,g)}function Lt(e,t){_(e,0,2),E(e,0,0,["..................","..kk..........kk..","..kkk.kkkkkk.kkk..","...kkkkkkkkkkkk...","...kkkkkbbkkkkk...","..oooooooooooooo.."],{k,b:t?k:g,o:I})}function Ut(e,t){let o=D(t,e);for(let s=t.T+1;s<=o;s+=2)for(let r=0;rs.padEnd(18,".").slice(0,18)),o)}function Vt(e,t){let o=t.T;for(let r=0;re<0?0:e>255?255:Math.round(e);function R(e,t=1.22,o=.68){return[[j(e[0]*t),j(e[1]*t),j(e[2]*t)],[e[0],e[1],e[2]],[j(e[0]*o),j(e[1]*o),j(e[2]*o)]]}var Re=(e,t,o)=>[Math.round(e[0]*(1-o)+t[0]*o),Math.round(e[1]*(1-o)+t[1]*o),Math.round(e[2]*(1-o)+t[2]*o)];function n(e,t,o,s,r=255){if(t<0||t>=H||o<0||o>=K)return;let a=(o*H+t)*4;e[a]=s[0],e[a+1]=s[1],e[a+2]=s[2],e[a+3]=r}var Le=(e,t,o)=>n(e,t,o,[0,0,0],0);function S(e,t,o){return t<0||t>=H||o<0||o>=K?0:e[(o*H+t)*4+3]}function b(e,t,o){let s=(o*H+t)*4;return[e[s],e[s+1],e[s+2]]}function q(e,t){return e[0]===t[0]&&e[1]===t[1]&&e[2]===t[2]}function y(e,t,o,s,r,a){for(let i=o;i<=r;i++)for(let c=t;c<=s;c++)n(e,c,i,a)}function $(e,t,o,s,r){S(e,t,o)===255&&q(b(e,t,o),s)&&n(e,t,o,r)}var ae={pale:{hi:[255,238,224],base:[252,226,208],sh:[226,188,166],line:[186,136,112]},light:{hi:[255,221,189],base:[247,201,170],sh:[212,158,126],line:[168,112,82]},tan:{hi:[232,182,136],base:[214,162,116],sh:[176,126,86],line:[138,92,60]},brown:{hi:[180,130,94],base:[158,112,78],sh:[124,86,58],line:[90,60,40]},dark:{hi:[142,98,70],base:[120,80,56],sh:[94,62,42],line:[64,42,28]},deep:{hi:[112,74,52],base:[90,58,40],sh:[66,40,27],line:[44,26,16]}},To=Object.fromEntries(Object.keys(ae).map(e=>[e,ae[e].base])),P=e=>{var t;return(t=ae[e])!=null?t:ae.light};function eo(e,t){let o=P(t);for(let s=4;s<=16;s++)for(let r=p;r<=u;r++)(r===p||r===u)&&(s===4||s===5||s===16)||(r===5||r===12)&&s===4||n(e,r,s,o.base);for(let s=6;s<12;s++)n(e,5,s,o.hi);n(e,6,5,o.hi),n(e,7,5,o.hi);for(let s=6;s<15;s++)n(e,12,s,o.sh);for(let s of[7,8,9,10,11])n(e,s,16,o.sh);for(let s of[p-1,u+1])n(e,s,9,o.base),n(e,s,10,o.base),n(e,s,11,o.sh);y(e,7,17,10,18,o.sh),y(e,7,17,9,17,o.base)}function to(e,t){let o=P(t);for(let s=11;s<=15;s++)n(e,p-1,s,o.base),n(e,u+1,s,o.base);n(e,p-1,15,o.sh),n(e,u+1,15,o.sh);for(let s of[5,6,11,12])n(e,s,16,o.base);y(e,6,17,11,18,o.base);for(let s of[6,7,8,9,10,11])n(e,s,18,o.sh);n(e,7,17,o.sh),n(e,10,17,o.sh)}function oo(e,t,o){let s=P(t);if(o==="heavy"){to(e,t);return}if(o==="round"){for(let r=10;r<=14;r++)n(e,p-1,r,s.base),n(e,u+1,r,s.sh);n(e,p-1,10,s.hi),n(e,p-1,14,s.sh)}else if(o==="square")y(e,p,16,u,16,s.base),n(e,u,16,s.sh),y(e,5,17,12,17,s.base),n(e,12,17,s.sh),n(e,5,17,s.sh),y(e,7,18,10,18,s.sh);else if(o==="long")y(e,5,16,12,16,s.base),n(e,12,16,s.sh),y(e,5,17,12,17,s.base),n(e,12,17,s.sh),y(e,7,18,10,18,s.base),n(e,10,18,s.sh),y(e,7,19,10,19,s.sh);else if(o==="heart"){for(let[r,a]of[[4,14],[13,14],[4,15],[13,15],[5,16],[12,16]])Le(e,r,a);n(e,6,16,s.sh),n(e,11,16,s.sh)}}function so(e,t){var A,X,ge,Be,ve,we;let o=P(t.skin),s=[250,248,244],r=(A=t.eyec)!=null?A:[46,38,42],a=[252,250,248],i=[54,40,48],c=(X=t.eyes)!=null?X:"normal";if(c==="normal"||c==="lashed"||c==="wide"){for(let x of[5,6,10,11])n(e,x,9,s);n(e,6,9,r),n(e,10,9,r)}if(c==="wide"){for(let x of[5,6,10,11])n(e,x,8,s);n(e,6,8,r),n(e,10,8,r)}if(c==="lashed"){for(let x of[5,6,10,11])n(e,x,8,i);n(e,4,8,i),n(e,12,8,i),n(e,5,9,a),n(e,10,9,a)}if(c==="dot"&&(n(e,6,9,r),n(e,10,9,r)),c==="squint")for(let x of[5,6,10,11])n(e,x,9,r);c==="happy"&&(n(e,5,9,r),n(e,6,8,r),n(e,10,8,r),n(e,11,9,r));let h=(ge=t.brow)!=null?ge:"flat";if(h==="flat")for(let x of[5,6,10,11])n(e,x,7,o.line);else if(h==="angry")n(e,5,8,o.line),n(e,6,7,o.line),n(e,10,7,o.line),n(e,11,8,o.line);else if(h==="raised")for(let x of[5,6,10,11])n(e,x,6,o.line);else if(h==="soft"){for(let x of[5,11])n(e,x,7,o.line);for(let x of[6,10])n(e,x,7,o.sh)}else if(h==="thick"){for(let x of[4,5,6,10,11,12])n(e,x,7,o.line);n(e,5,6,o.line),n(e,11,6,o.line)}else h==="arched"&&(n(e,5,7,o.line),n(e,6,6,o.line),n(e,10,6,o.line),n(e,11,7,o.line));let l=(Be=t.nose)!=null?Be:"normal";l==="normal"?(n(e,8,11,o.sh),n(e,8,12,o.sh),n(e,7,12,o.sh)):l==="small"?n(e,8,12,o.sh):l==="long"?(n(e,8,10,o.sh),n(e,8,11,o.sh),n(e,8,12,o.sh),n(e,7,12,o.sh)):l==="wide"&&(n(e,8,11,o.sh),n(e,7,12,o.sh),n(e,8,12,o.sh),n(e,9,12,o.sh));let m=(ve=t.mouthc)!=null?ve:[158,86,80],f=(we=t.mouth)!=null?we:"neutral",d={neutral:[[7,14],[8,14],[9,14],[10,14]],smile:[[7,14],[8,14],[9,14],[10,14],[6,13],[11,13]],frown:[[7,15],[8,15],[9,15],[10,15],[6,14],[11,14]],grin:[[7,14],[8,14],[9,14],[10,14],[7,13],[8,13],[9,13],[10,13],[6,13],[11,13]],smirk:[[7,14],[8,14],[9,14],[10,14],[11,13]],open:[]};for(let[x,Ve]of d[f])n(e,x,Ve,m);if(f==="open"&&(y(e,8,13,9,14,[88,40,46]),n(e,7,14,m),n(e,10,14,m)),t.blush)for(let x of[5,12])n(e,x,12,[235,150,140],140)}var no=(e,t,o,s)=>{var m,f;let[r,a,i]=R(t),c=(m=s.part)!=null?m:"L",h=(f=s.recede)!=null?f:0;y(e,p,2,u,4,a);for(let d=p-1;d<=u+1;d++)n(e,d,3,a);y(e,p-1,4,u+1,5,a);for(let d=6;d<9;d++)n(e,p-1,d,a),n(e,p,d,a),n(e,u,d,a),n(e,u+1,d,a);for(let d=p;d<=u;d++)n(e,d,5,a);if(h){for(let d=3;d<6;d++)for(let A=6;A<12;A++)q(b(e,A,d),a)&&n(e,A,d,o);n(e,8,5,a)}let l=c==="L"?6:11;for(let d=2;d<6;d++)n(e,l,d,i);for(let d=p;d{let[o,s]=R(t);y(e,p,2,u,4,s);for(let r=p-1;r<=u+1;r++)n(e,r,3,s);y(e,p-1,4,u+1,5,s);for(let r=p;r<=u;r++)n(e,r,5,s);for(let r=6;r<=12;r++)n(e,r,6,s);n(e,9,7,s),n(e,10,7,s),n(e,11,7,s);for(let r=6;r<9;r++)n(e,p-1,r,s),n(e,p,r,s),n(e,u,r,s),n(e,u+1,r,s);for(let r=p;r<=u;r++)S(e,r,2)&&n(e,r,2,o);for(let r of[7,8,9])n(e,r,6,o)},ao=(e,t,o,s)=>{var l,m;let[r,a,i]=R(t),c=(l=s.length)!=null?l:17,h=(m=s.vol)!=null?m:1;y(e,p-1,2,u+1,5,a);for(let f=p-1;f<=u+1;f++)n(e,f,3,a);for(let f=p;f<=u;f++)n(e,f,5,a);for(let f=6;f<12;f++)n(e,f,6,a);n(e,8,6,o),n(e,9,6,o);for(let f=6;f<=c;f++){for(let d=0;d{let[s,r]=R(t);y(e,p,3,u,5,r);for(let a=p-1;a<=u+1;a++)n(e,a,4,r);for(let a=p;a<=u;a++)n(e,a,5,r);for(let a=6;a<12;a++)n(e,a,6,r);n(e,8,6,o),n(e,9,6,o);for(let a=6;a<9;a++)n(e,p,a,r),n(e,u,a,r);y(e,7,1,10,2,r);for(let a=p;a<=u;a++)S(e,a,3)&&n(e,a,3,s)},co=(e,t,o)=>{let[s,r]=R(t),a=[[4,3],[5,2],[6,3],[7,2],[8,3],[9,2],[10,3],[11,2],[12,3],[13,3],[3,4],[4,4],[13,4],[14,4],[3,5],[4,5],[13,5],[14,5],[3,6],[13,6],[4,6],[12,6],[3,7],[13,7],[4,7]];y(e,p,3,u,5,r);for(let i=p-1;i<=u+1;i++)n(e,i,4,r);for(let[i,c]of a)n(e,i,c,r);for(let i=6;i<12;i++)n(e,i,6,r);n(e,8,6,o),n(e,9,6,o);for(let[i,c]of[[5,2],[7,2],[9,2],[11,2]])n(e,i,c,s)},lo=(e,t,o,s)=>{var h;let[r,a]=R(t),i=(h=s.length)!=null?h:8;y(e,p-1,2,u+1,5,a);let c=[[3,2],[5,1],[7,2],[9,1],[11,2],[13,1],[14,2],[4,2],[12,2]];for(let[l,m]of c)n(e,l,m,a);for(let l=p;l<=u;l++)n(e,l,5,a);for(let l=6;l<12;l++)n(e,l,6,a);n(e,8,6,o),n(e,9,6,o);for(let l=6;l<=i;l++)n(e,p-1,l,a),n(e,p,l,a),n(e,u,l,a),n(e,u+1,l,a);for(let[l,m]of c)n(e,l,m,r)},ho=(e,t,o)=>{let[,s,r]=R(t);for(let a=4;a<10;a++)n(e,p-1,a,s),n(e,p,a,s),n(e,u,a,s),n(e,u+1,a,s);for(let a=p;a<=u;a++)n(e,a,4,s);for(let a=p+1;a{let[s,r]=R(t);y(e,p,3,u,5,r);for(let i=p-1;i<=u+1;i++)n(e,i,4,r);for(let i=p;i<=u;i++)n(e,i,5,r);let a=[[5,2],[7,1],[9,2],[11,1],[6,2],[8,2],[10,2],[12,2]];for(let[i,c]of a)n(e,i,c,r);for(let i=6;i<12;i++)n(e,i,6,r);n(e,8,6,o),n(e,9,6,o);for(let i=6;i<8;i++)n(e,p,i,r),n(e,u,i,r);for(let[i,c]of a)n(e,i,c,s)},mo=(e,t,o,s)=>{let[r,a,i]=R(o,1.1,.82);for(let m=6;m<=11;m++)n(e,m,2,a);for(let m=5;m<=12;m++)n(e,m,3,a);for(let m=p;m<=u;m++)n(e,m,4,a);for(let m of[7,8,9])n(e,m,2,r);n(e,6,3,r),n(e,7,3,r),n(e,5,3,i),n(e,12,3,i),n(e,u,4,i);let[,c,h]=R(t),l=s.recede?8:6;for(let m=l;m<=10;m++)n(e,p-1,m,c),n(e,p,m,c),n(e,u,m,c),n(e,u+1,m,c);for(let m=l;m<=10;m++)n(e,p-1,m,h),n(e,u+1,m,h)},yo=(e,t,o,s)=>{var h;let[r,a,i]=R(t),c=(h=s.length)!=null?h:23;y(e,5,2,12,2,a),y(e,4,3,13,4,a);for(let l=6;l<=9;l++)n(e,l,2,r);for(let l=5;l<=7;l++)n(e,4,l,a),n(e,13,l,i);for(let l=4;l<=c;l++)n(e,3,l,l<=9?r:a),n(e,14,l,i);for(let l=8;l<=c;l++)n(e,2,l,a),n(e,15,l,i)},uo=(e,t)=>{let[o,s,r]=R(t);y(e,5,0,12,0,s),y(e,3,1,14,1,s),y(e,2,2,15,4,s);for(let a=5;a<=12;a++)y(e,2,a,3,a,s),y(e,14,a,15,a,s);n(e,2,13,s),n(e,15,13,s),n(e,4,5,s),n(e,13,5,s);for(let a=2;a<=13;a++)n(e,15,a,r);for(let a=5;a<=12;a++)n(e,14,a,a%2?r:s);for(let[a,i]of[[5,0],[6,0],[3,1],[4,1],[2,2],[3,2],[2,3],[2,5],[3,7],[2,9]])n(e,a,i,o)},po=(e,t,o)=>{let s=Re(R(t)[1],o,.45);y(e,5,3,12,3,s),y(e,4,4,13,4,s),n(e,4,5,s),n(e,13,5,s)},ko=(e,t,o)=>{let[s,r,a]=R(t),i=Re(r,o,.5);y(e,4,4,13,4,i),n(e,4,5,i),n(e,13,5,i),y(e,8,0,9,3,r),n(e,7,3,r),n(e,10,3,r),n(e,8,0,s),n(e,8,1,s),n(e,9,3,a),n(e,10,3,a)},Ro={styleShort:no,styleFloppy:ro,styleFrame:ao,styleBun:io,styleCurly:co,styleMessy:lo,styleRecede:ho,styleSpiky:fo,styleBald:mo,styleLong:yo,styleAfro:uo,styleBuzz:po,styleMohawk:ko};function xo(e,t,o){let[,s,r]=R(o);if(t==="mustache"){for(let a of[6,7,8,9,10])n(e,a,13,s);n(e,6,12,s),n(e,10,12,s)}else if(t==="mustacheSm")for(let a of[7,8,9])n(e,a,13,s);else if(t==="stubble")for(let[a,i]of[[5,14],[6,15],[7,15],[8,15],[9,15],[10,15],[11,14],[12,13],[4,13],[5,15],[10,15]])n(e,a,i,r,150);else if(t==="goatee"){for(let a of[8,9])n(e,a,15,s);n(e,8,14,s),n(e,9,14,s);for(let a of[7,8,9,10])n(e,a,13,s)}}function go(e){let t=[60,54,62],o=[236,240,246];for(let s of[5,6])n(e,s,8,t),n(e,s,10,t);n(e,4,9,t),n(e,7,9,t),n(e,4,8,t),n(e,7,8,t);for(let s of[10,11])n(e,s,8,t),n(e,s,10,t);n(e,9,9,t),n(e,12,9,t),n(e,9,8,t),n(e,12,8,t),n(e,8,8,t),n(e,3,9,t),n(e,13,9,t),n(e,4,8,o),n(e,9,8,o)}function Bo(e,t,o=!1){let[,s,r]=R(t),a=o?[[19,5,12],[20,3,14],[21,2,15],[22,1,16],[23,1,16],[24,0,17],[25,0,17],[26,0,17],[27,0,17]]:[[19,6,11],[20,4,13],[21,3,14],[22,2,15],[23,2,15],[24,1,16],[25,1,16],[26,1,16],[27,1,16]];for(let[h,l,m]of a)y(e,l,h,m,h,s);let[i,c]=o?[1,16]:[2,15];for(let h=22;h<28;h++)n(e,i,h,r),n(e,c,h,r)}function vo(e,t,o,s,r,a,i=!1){let[c,h,l]=R(o);if(Bo(e,o,i),t==="suit"){let m=[238,238,236];for(let[f,d]of[[8,19],[9,19],[7,20],[8,20],[9,20],[10,20],[8,21],[9,21]])n(e,f,d,m);for(let[f,d]of[[6,20],[7,21],[11,20],[10,21],[6,21],[11,21]])n(e,f,d,l);if(r){for(let f=20;f<26;f++)n(e,8,f,r),n(e,9,f,r);n(e,8,20,R(r)[0])}else for(let f=22;f<26;f++)n(e,8,f,m),n(e,9,f,m)}else if(t==="dressshirt"){for(let[m,f]of[[6,19],[7,19],[10,19],[11,19],[7,20],[10,20]])n(e,m,f,l);for(let m=20;m<27;m+=2)n(e,8,m,l);if(r)for(let m=19;m<26;m++)n(e,8,m,r),n(e,9,m,r)}else if(t==="polo"){for(let[f,d]of[[6,19],[7,19],[10,19],[11,19]])n(e,f,d,c);n(e,8,20,l),n(e,8,22,l);let m=s?R(s)[1]:c;for(let[f,d]of[[7,20],[9,20]])n(e,f,d,m)}else if(t==="blouse"){let m=P(a);for(let[f,d]of[[7,19],[8,19],[9,19],[10,19],[8,20],[9,20]])n(e,f,d,m.sh);for(let f=5;f<13;f++)q(b(e,f,20),h)&&n(e,f,20,c)}else if(t==="cardigan"){let m=s?R(s)[1]:[235,233,226];for(let f=19;f<27;f++)n(e,8,f,m),n(e,9,f,m);for(let[f,d]of[[6,19],[7,19],[10,19],[11,19]])n(e,f,d,l)}else if(t==="sweater")for(let[m,f]of[[6,19],[7,19],[8,19],[9,19],[10,19],[11,19]])n(e,m,f,l)}function wo(e,t){y(e,7,18,10,19,P(t).sh)}var He=[238,238,236],pe=[235,233,226],Ue={shirt_open:{draw(e,t,o){for(let[s,r]of[[5,t],[6,t],[11,t],[12,t],[6,t+1],[11,t+1]])n(e,s,r,o.sh);for(let[s,r]of[[8,t],[9,t],[8,t+1],[9,t+1]])n(e,s,r,o.skin.base);n(e,8,t+3,o.sh),n(e,8,t+5,o.sh)}},tee:{draw(e,t,o){for(let s of[7,8,9,10])n(e,s,t,o.sh);o.c2&&y(e,8,t+3,9,t+4,o.c2)}},vest:{draw(e,t,o){for(let[s,r]of[[8,t],[9,t],[7,t+1],[8,t+1],[9,t+1],[10,t+1],[8,t+2],[9,t+2],[6,t],[11,t]])n(e,s,r,He);for(let[s,r]of[[7,t+2],[10,t+2],[7,t+3],[10,t+3]])n(e,s,r,o.sh);if(n(e,8,t+4,o.sh),n(e,8,t+6,o.sh),o.tie)for(let s=t+1;s<=t+6;s++)n(e,8,s,o.tie),n(e,9,s,o.tie)}},blazer_tee:{draw(e,t,o){var r;let s=(r=o.c2)!=null?r:pe;y(e,7,t,10,t+6,s),n(e,8,t,R(s)[2]),n(e,9,t,R(s)[2]);for(let[a,i]of[[6,t],[11,t],[7,t+1],[10,t+1]])n(e,a,i,o.sh)}},turtleneck:{over(e,t){y(e,6,17,11,18,t.base);for(let o of[7,8,9,10])n(e,o,17,t.hi);n(e,6,17,t.sh),n(e,11,17,t.sh),n(e,11,18,t.sh)}},hoodie:{draw(e,t,o){for(let s of[7,10])for(let r=t+1;r<=t+3;r++)n(e,s,r,o.hi);y(e,6,t+5,11,t+5,o.sh),n(e,6,t+6,o.sh),n(e,11,t+6,o.sh)},over(e,t,o){let s=o?5:6,r=o?12:11;y(e,s,17,6,17,t.base),y(e,11,17,r,17,t.base),y(e,s-1,18,7,18,t.base),y(e,10,18,r+1,18,t.base),n(e,s-1,18,t.sh),n(e,r+1,18,t.sh),n(e,8,18,t.skin.sh),n(e,9,18,t.skin.sh)}},boatneck:{draw(e,t,o){for(let s of[6,7,8,9,10,11])n(e,s,t,o.skin.sh);for(let s=5;s<=12;s++)$(e,s,t+1,o.base,o.hi)}},dress:{draw(e,t,o){n(e,8,t,o.skin.sh),n(e,9,t,o.skin.sh),n(e,7,t,o.sh),n(e,10,t,o.sh);for(let s=5;s<=12;s++)$(e,s,t+5,o.base,o.sh)},skirt(e,t){let o=t.heavy?3:4,s=t.heavy?14:13;y(e,o,25,s,28,t.base);for(let r=25;r<=28;r++)n(e,o,r,t.hi),n(e,s,r,t.sh),n(e,s-1,r,t.sh)}},wrap:{draw(e,t,o){for(let[s,r]of[[5,t],[6,t+1],[7,t+2],[8,t+3],[12,t],[11,t+1],[10,t+2],[9,t+3]])n(e,s,r,o.sh);for(let[s,r]of[[7,t],[8,t],[9,t],[10,t],[8,t+1],[9,t+1]])n(e,s,r,o.skin.sh);for(let s=6;s<=11;s++)$(e,s,t+5,o.base,o.hi)}},blazer_w:{draw(e,t,o){var r;let s=(r=o.c2)!=null?r:pe;for(let a=t+1;a<=t+6;a++)n(e,8,a,s),n(e,9,a,s);n(e,8,t,o.skin.sh),n(e,9,t,o.skin.sh);for(let[a,i]of[[6,t],[11,t],[7,t+1],[10,t+1]])n(e,a,i,o.sh)}},stripe_tee:{draw(e,t,o){var r;for(let a of[7,8,9,10])n(e,a,t,o.sh);let s=(r=o.c2)!=null?r:pe;for(let a of[t+2,t+4,t+6])for(let i=4;i<=13;i++)$(e,i,a,o.base,s)}},cami:{draw(e,t,o){for(let s of[7,8,9,10])n(e,s,t,o.skin.sh);for(let[s,r]of[[4,t+1],[5,t+1],[12,t+1],[13,t+1],[3,t+2],[4,t+2],[13,t+2],[14,t+2]])$(e,s,r,o.base,o.skin.base);for(let[s,r]of[[13,t+1],[14,t+2]])$(e,s,r,o.skin.base,o.skin.sh)}},sweater_v:{draw(e,t,o){var r;let s=(r=o.c2)!=null?r:He;for(let[a,i]of[[7,t],[8,t],[9,t],[10,t],[8,t+1],[9,t+1]])n(e,a,i,s);for(let[a,i]of[[6,t],[11,t],[7,t+1],[10,t+1],[8,t+2],[9,t+2]])n(e,a,i,o.sh)}},shirt_w:{draw(e,t,o){for(let[s,r]of[[6,t],[11,t]])n(e,s,r,o.hi);for(let[s,r]of[[7,t],[10,t]])n(e,s,r,o.sh);n(e,8,t,o.skin.sh),n(e,9,t,o.skin.sh);for(let s=t+1;s<=t+6;s+=2)n(e,8,s,o.sh)}}};function ze(e){let[t,o,s]=R(e.c1);return{hi:t,base:o,sh:s,c1:e.c1,c2:e.c2,tie:e.tie,skin:P(e.skin),heavy:e.face==="heavy"}}var Fe=[44,40,48];function Co(e,t,o){let[,s,r]=R(t);for(let[c,h]of[[5,7],[10,12]]){y(e,c,25,h,30,s);for(let l=25;l<=30;l++)n(e,h,l,r)}let a=o!==1,i=o!==2;y(e,5,a?31:30,7,a?31:30,Fe),y(e,10,i?31:30,12,i?31:30,Fe)}function Ao(e,t,o){let[s,r,a]=R(t.c1);if(t.heavy){y(e,3,18,14,18,r),y(e,2,19,15,19,r),y(e,2,20,15,24,r);for(let c=20;c<=24;c++)n(e,2,c,a),n(e,15,c,a),n(e,14,c,a)}else{y(e,4,18,13,18,r),y(e,3,19,14,19,r),y(e,4,20,13,24,r);for(let c=20;c<=24;c++)n(e,3,c,a),n(e,14,c,a),n(e,13,c,a)}if(o){y(e,6,18,11,18,a);for(let c=19;c<=24;c++)n(e,8,c,a);return}let i=P(t.skin);if(t.cloth==="suit"){let c=[238,238,236];for(let[h,l]of[[8,18],[9,18],[7,19],[8,19],[9,19],[10,19],[8,20],[9,20]])n(e,h,l,c);for(let[h,l]of[[6,19],[7,20],[11,19],[10,20]])n(e,h,l,a);if(t.tie){for(let h=19;h<=24;h++)n(e,8,h,t.tie),n(e,9,h,t.tie);n(e,8,19,R(t.tie)[0])}}else if(t.cloth==="dressshirt"){for(let[c,h]of[[6,18],[7,18],[10,18],[11,18],[7,19],[10,19]])n(e,c,h,a);if(t.tie)for(let c=18;c<=24;c++)n(e,8,c,t.tie),n(e,9,c,t.tie);else for(let c=20;c<=24;c+=2)n(e,8,c,a)}else if(t.cloth==="polo"){for(let[c,h]of[[6,18],[7,18],[10,18],[11,18]])n(e,c,h,s);n(e,8,19,a),n(e,8,21,a)}else if(t.cloth==="blouse"){for(let[c,h]of[[7,18],[8,18],[9,18],[10,18],[8,19],[9,19]])n(e,c,h,i.sh);for(let c=5;c<13;c++)q(b(e,c,19),r)&&n(e,c,19,s)}else if(t.cloth==="cardigan"){let c=t.c2?R(t.c2)[1]:[235,233,226];for(let h=18;h<=24;h++)n(e,8,h,c),n(e,9,h,c);for(let[h,l]of[[6,18],[7,18],[10,18],[11,18]])n(e,h,l,a)}else if(t.cloth==="sweater")for(let[c,h]of[[6,18],[7,18],[8,18],[9,18],[10,18],[11,18]])n(e,c,h,a)}function Go(e,t){var h,l,m,f;let o=P(t.skin);if(t.hair==="styleBald"){be(e,t);return}let[s,r,a]=R(t.hairc),i=[[2,6,11],[3,5,12],[4,4,13],[5,4,13],[6,4,13],[7,4,13],[8,4,13],[9,4,13],[10,4,13],[11,4,13],[12,4,13],[13,5,12],[14,6,11]];for(let[d,A,X]of i)y(e,A,d,X,d,r);let c=t.hair==="styleFrame"?(l=(h=t.hairargs)==null?void 0:h.length)!=null?l:17:t.hair==="styleMessy"?(f=(m=t.hairargs)==null?void 0:m.length)!=null?f:9:0;for(let d=11;d<=c;d++)n(e,p-1,d,r),n(e,p,d,r),n(e,u,d,r),n(e,u+1,d,r);for(let d=4;d<=12;d++)n(e,4,d,a),n(e,13,d,a);for(let[d,A]of[[5,3],[12,3],[5,13],[12,13],[6,14],[11,14]])n(e,d,A,a);for(let[d,A]of[[7,2],[8,2],[9,2],[10,2],[7,3],[8,3],[9,3]])n(e,d,A,s);for(let d=4;d<=11;d++)n(e,9,d,s);for(let d=4;d<=12;d++)n(e,8,d,a);y(e,7,14,10,14,a),y(e,7,15,10,17,o.sh),y(e,7,15,9,15,o.base)}function be(e,t){let o=P(t.skin),[s,r,a]=R(o.base,1.1,.82),i=[[2,6,11],[3,5,12],[4,4,13],[5,4,13],[6,4,13],[7,4,13],[8,4,13],[9,4,13],[10,4,13],[11,4,13],[12,4,13],[13,5,12],[14,6,11]];for(let[l,m,f]of i)y(e,m,l,f,l,r);for(let l=4;l<=12;l++)n(e,4,l,a),n(e,13,l,a);for(let[l,m]of[[7,2],[8,2],[9,2],[8,3],[9,4],[9,5]])n(e,l,m,s);let[,c,h]=R(t.hairc);for(let l=4;l<=13;l++)n(e,l,11,c),n(e,l,12,c);for(let l of[4,13])n(e,l,11,h),n(e,l,12,h);y(e,7,14,10,14,o.sh),y(e,7,15,10,17,o.sh),y(e,7,15,9,15,o.base)}function So(e,t){var i,c;let[o,s,r]=R(t.hairc),a=P(t.skin);if(t.hair==="styleBuzz"||t.hair==="styleMohawk"){be(e,t);let h=Re(s,a.base,t.hair==="styleBuzz"?.45:.5);for(let l=2;l<=12;l++)for(let m=4;m<=13;m++)S(e,m,l)===255&&!q(b(e,m,l),a.sh)&&n(e,m,l,h);t.hair==="styleMohawk"&&(y(e,8,1,9,12,s),n(e,8,1,o),n(e,8,2,o));return}if(Go(e,t),t.hair==="styleLong"){let h=(c=(i=t.hairargs)==null?void 0:i.length)!=null?c:23;for(let l=11;l<=h;l++)n(e,2,l,s),n(e,3,l,s),n(e,4,l,s),n(e,13,l,r),n(e,14,l,r),n(e,15,l,r);for(let l=15;l<=h;l++)for(let m=5;m<=12;m++)n(e,m,l,m===8?r:s)}else if(t.hair==="styleAfro"){y(e,5,0,12,0,s),y(e,3,1,14,1,s),y(e,2,2,15,3,s);for(let h=4;h<=12;h++)y(e,2,h,3,h,s),y(e,14,h,15,h,r);for(let h=2;h<=13;h++)n(e,15,h,r);for(let[h,l]of[[5,0],[6,0],[3,1],[2,2],[2,3]])n(e,h,l,o)}}function We(e){let t=[];for(let o=0;on(e,t,o,s),clear:(t,o)=>Le(e,t,o),alpha:(t,o)=>S(e,t,o),rgb:(t,o)=>b(e,t,o)}}var ke=null;function Ke(e){ke=e}function Eo(){var e,t,o;if(ke!==null)return ke;try{return((o=(t=(e=globalThis.document)==null?void 0:e.documentElement)==null?void 0:t.dataset)==null?void 0:o.cthSeason)==="halloween"}catch{return!1}}var Oo=new Map(Object.entries(xe).map(([e,t])=>[t,e]));function qe(e){if(!Eo())return null;let t=Oo.get(e);return t&&Pe[t]||_e(e)}var Te=(e,t)=>t?`${T(e)}|costume:${t}`:T(e);function Xe(e){let t=qe(e),o=L(e),s=Te(o,t),r=oe.get(s);return r||(oe.size>=je&&oe.clear(),r=_o(o,t),oe.set(s,r)),r}function Ye(e){let t=qe(e),o=L(e),s=Te(o,t),r=se.get(s);return r||(se.size>=je&&se.clear(),r={front:[0,1,2].map(a=>Ie(o,a,!1,t)),back:[0,1,2].map(a=>Ie(o,a,!0,t))},se.set(s,r)),r}Ke(!1);globalThis.MDAv={costumeFrames:(e,c)=>({front:[0,1,2].map(a=>Ie(L(e),a,!1,c)),back:[0,1,2].map(a=>Ie(L(e),a,!0,c))}),CAST_RECIPES:xe,portraitBufFor:Xe,sceneFrameBufsFor:Ye,randomRecipe:ce,presetRecipe:Y,PRESET_COUNT:le,PORTRAIT_W:U,PORTRAIT_H:z,SCENE_W:ne,SCENE_H:re};})(); diff --git a/docs/assets/brands.js b/docs/assets/brands.js new file mode 100644 index 0000000000..35007dd499 --- /dev/null +++ b/docs/assets/brands.js @@ -0,0 +1,2 @@ +/* Vendor marks. Engine marks: the live harnessmd.com page and the app (src/renderer/src/assets/logos/limits, rc/0.5.8). Integration marks: Simple Icons (CC0) as shipped in react-icons 5.6.0 (MIT). Marks belong to their owners. */ +window.MDBrands={"engines":{"claude":{"name":"Claude Code","logo":"","bg":"#F0EEE6","fg":"#141413","sub":"#6F6B60","chip":"rgba(20,20,19,.07)","plan":"With your own Claude plan"},"codex":{"name":"Codex","logo":"","bg":"#0D0D0D","fg":"#FFFFFF","sub":"#A3A3A3","chip":"rgba(255,255,255,.1)","plan":"With your own ChatGPT plan"},"gemini":{"name":"Gemini CLI","logo":"","bg":"#FFFFFF","fg":"#1F1F1F","sub":"#5F6368","chip":"#F1F3F4","plan":"With your own Google account"},"grok":{"name":"Grok","logo":"","bg":"#000000","fg":"#FFFFFF","sub":"#9A9A9A","chip":"rgba(255,255,255,.1)","plan":"With your own Grok plan"},"copilot":{"name":"Copilot","logo":"","bg":"#0D1117","fg":"#E6EDF3","sub":"#8B949E","chip":"rgba(240,246,252,.1)","plan":"With your own GitHub Copilot plan"},"cursor":{"name":"Cursor","logo":"","bg":"#14120B","fg":"#EDECEC","sub":"#9C9A92","chip":"rgba(237,236,236,.1)","plan":"With your own Cursor plan"},"antigravity":{"name":"Antigravity","logo":" ","bg":"#FFFFFF","fg":"#1F1F1F","sub":"#5F6368","chip":"#F1F3F4","plan":"With your own Google account"},"kimi":{"name":"Kimi Code","logo":"","bg":"#000000","fg":"#FFFFFF","sub":"#9A9A9A","chip":"rgba(255,255,255,.1)","plan":"With your own sign in"},"qwen":{"name":"Qwen","logo":"","bg":"linear-gradient(135deg,#615CED,#8B6CF6)","fg":"#FFFFFF","sub":"rgba(255,255,255,.75)","chip":"rgba(255,255,255,.16)","plan":"With your own key"},"opencode":{"name":"OpenCode","logo":" ","bg":"#131010","fg":"#F1ECEC","sub":"#A39C9C","chip":"rgba(241,236,236,.1)","plan":"With the provider you use there"},"crush":{"name":"Crush","logo":"","bg":"#1F1638","fg":"#F4EFFF","sub":"#B9A9E6","chip":"rgba(195,181,255,.14)","plan":"With your own key"},"pi":{"name":"Pi","logo":"","bg":"#F6F6F4","fg":"#111111","sub":"#6B6B6B","chip":"#EBEBE8","plan":"With your own key"}},"apps":{"slack":{"name":"Slack","color":"#4A154B","logo":""},"gmail":{"name":"Gmail","color":"#EA4335","logo":""},"gcal":{"name":"Google Calendar","color":"#4285F4","logo":""},"gdrive":{"name":"Google Drive","color":"#4285F4","logo":""},"github":{"name":"GitHub","color":"#181717","logo":""},"notion":{"name":"Notion","color":"#000000","logo":""},"linear":{"name":"Linear","color":"#5E6AD2","logo":""},"figma":{"name":"Figma","color":"#F24E1E","logo":""},"hubspot":{"name":"HubSpot","color":"#FF7A59","logo":""},"stripe":{"name":"Stripe","color":"#635BFF","logo":""},"sentry":{"name":"Sentry","color":"#362D59","logo":""},"supabase":{"name":"Supabase","color":"#3FCF8E","logo":""},"atlassian":{"name":"Atlassian","color":"#0052CC","logo":""},"brave":{"name":"Brave Search","color":"#FB542B","logo":""},"canva":{"name":"Canva","color":"#00C4CC","logo":""},"airtable":{"name":"Airtable","color":"#18BFFF","logo":""}},"mini":{"claude":"","codex":"","gemini":"","grok":"","cursor":"","copilot":"","ph":""}}; diff --git a/docs/assets/engines/antigravity-icon-full-color.svg b/docs/assets/engines/antigravity-icon-full-color.svg new file mode 100644 index 0000000000..115bc5f0d2 --- /dev/null +++ b/docs/assets/engines/antigravity-icon-full-color.svg @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/assets/engines/claude-code-spark.svg b/docs/assets/engines/claude-code-spark.svg new file mode 100644 index 0000000000..13d9ded648 --- /dev/null +++ b/docs/assets/engines/claude-code-spark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/engines/grok-mark.svg b/docs/assets/engines/grok-mark.svg new file mode 100644 index 0000000000..de83888002 --- /dev/null +++ b/docs/assets/engines/grok-mark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/engines/openai-blossom.svg b/docs/assets/engines/openai-blossom.svg new file mode 100644 index 0000000000..8312f0987d --- /dev/null +++ b/docs/assets/engines/openai-blossom.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/engines/opencode-logo-dark-square.svg b/docs/assets/engines/opencode-logo-dark-square.svg new file mode 100644 index 0000000000..6a67f62717 --- /dev/null +++ b/docs/assets/engines/opencode-logo-dark-square.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/docs/assets/favicon-48.png b/docs/assets/favicon-48.png new file mode 100644 index 0000000000..8fd31508a6 Binary files /dev/null and b/docs/assets/favicon-48.png differ diff --git a/docs/assets/fonts/LICENSE.txt b/docs/assets/fonts/LICENSE.txt new file mode 100644 index 0000000000..92d8221ca2 --- /dev/null +++ b/docs/assets/fonts/LICENSE.txt @@ -0,0 +1,113 @@ +Bundled fonts and their licenses +================================ + +Munder Difflin ships three font files inside the application bundle, in +src/renderer/src/assets/fonts/. They are NOT ours. All three are licensed under +the SIL Open Font License, Version 1.1, whose full text follows the attributions +below. The OFL requires this notice to travel with every copy of the software. + + press-start-2p-latin-400.woff2 + Press Start 2P (latin subset, weight 400) + Copyright (c) 2012, Cody "CodeMan38" Boisclair (cody@zone38.net) + https://fonts.google.com/specimen/Press+Start+2P + + inter-latin-var.woff2 + Inter (latin subset, variable, weights 400 to 700) + Copyright (c) 2016 The Inter Project Authors + https://github.com/rsms/inter + + jetbrains-mono-latin-var.woff2 + JetBrains Mono (latin subset, variable, weights 400 to 700) + Copyright 2020 The JetBrains Mono Project Authors + https://github.com/JetBrains/JetBrainsMono + +No CJK or Arabic face is bundled: a full CJK face is 8 to 10 MB, and every +platform we ship on already carries one. The design tokens name those system +faces as fallbacks instead (see src/renderer/src/design/tokens.css). + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/docs/assets/fonts/inter-latin-var.woff2 b/docs/assets/fonts/inter-latin-var.woff2 new file mode 100644 index 0000000000..91dc3e8529 Binary files /dev/null and b/docs/assets/fonts/inter-latin-var.woff2 differ diff --git a/docs/assets/fonts/jetbrains-mono-latin-var.woff2 b/docs/assets/fonts/jetbrains-mono-latin-var.woff2 new file mode 100644 index 0000000000..2ca6ac6059 Binary files /dev/null and b/docs/assets/fonts/jetbrains-mono-latin-var.woff2 differ diff --git a/docs/assets/fonts/press-start-2p-latin-400.woff2 b/docs/assets/fonts/press-start-2p-latin-400.woff2 new file mode 100644 index 0000000000..cefec4c16c Binary files /dev/null and b/docs/assets/fonts/press-start-2p-latin-400.woff2 differ diff --git a/docs/assets/logo-mark.svg b/docs/assets/logo-mark.svg new file mode 100644 index 0000000000..efe2fb1e7f --- /dev/null +++ b/docs/assets/logo-mark.svg @@ -0,0 +1,12 @@ + + Munder Difflin mark + + + + + + M + D + diff --git a/docs/assets/logo.png b/docs/assets/logo.png new file mode 100644 index 0000000000..6f9923aec5 Binary files /dev/null and b/docs/assets/logo.png differ diff --git a/docs/assets/office-pattern-dark.svg b/docs/assets/office-pattern-dark.svg new file mode 100644 index 0000000000..48af47a788 --- /dev/null +++ b/docs/assets/office-pattern-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/assets/office-pattern.svg b/docs/assets/office-pattern.svg new file mode 100644 index 0000000000..f14d3c3d41 --- /dev/null +++ b/docs/assets/office-pattern.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/assets/office.js b/docs/assets/office.js new file mode 100644 index 0000000000..1916ac393b --- /dev/null +++ b/docs/assets/office.js @@ -0,0 +1,456 @@ +/* The office, drawn in code for this page. + Floors, walls, desks and props are painted here, pixel by pixel, so the page ships no tileset. + The people are the app's own: MDAv paints them from portraitArt.ts. + One engine serves every map on the page: the hero, the hiring strip, the sandbox and 24x7. */ +(function () { + 'use strict'; + var T = 16; + var A = window.MDAv; + var OUT = '#26222E'; + + /* ---------- small helpers ---------- */ + function mulberry(a) { return function () { a |= 0; a = a + 0x6D2B79F5 | 0; var t = Math.imul(a ^ a >>> 15, 1 | a); t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t; return ((t ^ t >>> 14) >>> 0) / 4294967296; }; } + function R(g, x, y, w, h, c) { g.fillStyle = c; g.fillRect(x, y, w, h); } + function box(g, x, y, w, h, fill, line) { R(g, x, y, w, h, line || OUT); R(g, x + 1, y + 1, w - 2, h - 2, fill); } + function lerp(a, b, t) { return a + (b - a) * t; } + function hexRgb(h) { h = h.replace('#', ''); return [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16)]; } + function mix(a, b, t) { var x = hexRgb(a), y = hexRgb(b); return 'rgb(' + Math.round(lerp(x[0], y[0], t)) + ',' + Math.round(lerp(x[1], y[1], t)) + ',' + Math.round(lerp(x[2], y[2], t)) + ')'; } + function canvas(w, h) { var c = document.createElement('canvas'); c.width = w; c.height = h; return c; } + + /* ---------- the app's people ---------- */ + var spriteCache = new Map(); + function bufCanvas(buf, w, h) { var c = canvas(w, h), x = c.getContext('2d'), im = x.createImageData(w, h); im.data.set(buf); x.putImageData(im, 0, 0); return c; } + function sprites(recipe) { + var k = JSON.stringify(recipe), s = spriteCache.get(k); + if (!s) { + var f = A.sceneFrameBufsFor(recipe); + s = { front: f.front.map(function (b) { return bufCanvas(b, A.SCENE_W, A.SCENE_H); }), back: f.back.map(function (b) { return bufCanvas(b, A.SCENE_W, A.SCENE_H); }) }; + spriteCache.set(k, s); + } + return s; + } + function portrait(recipe, scale) { + var c = bufCanvas(A.portraitBufFor(recipe), A.PORTRAIT_W, A.PORTRAIT_H); + c.className = 'pxa'; + if (scale) { c.style.width = (18 * scale) + 'px'; c.style.height = (28 * scale) + 'px'; } + return c; + } + function recipeOf(name) { return A.CAST_RECIPES[name] || A.presetRecipe(0); } + + /* ---------- layouts ---------- + Tiles are 16 px. The top `wall` rows are wall. A desk is two tiles wide; seat 'n' puts the agent + behind it facing you, seat 's' puts the agent in front of it with the screen showing. */ + var LAYOUTS = { + wide: { + cols: 28, rows: 15, wall: 3, door: { x: 5 }, + windows: [{ x: 1, w: 3 }, { x: 8, w: 3 }, { x: 15, w: 3 }, { x: 22, w: 3 }], + board: { x: 11, w: 3 }, clock: { x: 19 }, + desks: [ + { x: 8, y: 6, seat: 'n' }, { x: 11, y: 6, seat: 'n' }, { x: 14, y: 6, seat: 'n' }, { x: 17, y: 6, seat: 'n' }, + { x: 8, y: 9, seat: 's' }, { x: 11, y: 9, seat: 's' }, { x: 14, y: 9, seat: 's' }, { x: 17, y: 9, seat: 's' }, + { x: 23, y: 5, seat: 'n', boss: true }, + { x: 8, y: 13, seat: 'n' }, { x: 11, y: 13, seat: 'n' }, { x: 14, y: 13, seat: 'n' }, { x: 17, y: 13, seat: 'n' } + ], + glass: { x: 21, y: 3, w: 7, h: 5, gap: 23 }, + props: [ + { k: 'counter', x: 1, y: 3, w: 3 }, { k: 'cooler', x: 0, y: 6 }, { k: 'plant', x: 0, y: 3 }, { k: 'plant', x: 27, y: 9 }, + { k: 'rug', x: 1, y: 9, w: 5, h: 4 }, { k: 'sofa', x: 1, y: 13, w: 3 }, { k: 'plant', x: 0, y: 13 }, { k: 'arcade', x: 0, y: 9 }, { k: 'bag', x: 4, y: 10 }, { k: 'bag', x: 2, y: 10 }, + { k: 'table', x: 21, y: 11, w: 4, h: 2 }, { k: 'printer', x: 26, y: 13 }, { k: 'shelf', x: 26, y: 3, w: 2 }, { k: 'plant', x: 6, y: 13 }, { k: 'files', x: 19, y: 3 }, { k: 'files', x: 20, y: 3 }, { k: 'plant', x: 27, y: 13 } + ], + errands: [ + { x: 2, y: 4, face: 'up', kind: 'coffee' }, { x: 1, y: 6, face: 'down', kind: 'cooler' }, { x: 26, y: 12, face: 'up', kind: 'printer' }, + { x: 9, y: 3, face: 'up', kind: 'window' }, { x: 16, y: 3, face: 'up', kind: 'window' }, { x: 22, y: 10, face: 'down', kind: 'chat' }, + { x: 24, y: 10, face: 'down', kind: 'chat' }, { x: 3, y: 12, face: 'up', kind: 'rug' }, { x: 12, y: 3, face: 'up', kind: 'board' }, { x: 1, y: 11, face: 'up', kind: 'arcade' } + ], + ambient: { x: 24, y: 12 }, posters: [6.5, 18.2, 25.6] + }, + tall: { + cols: 14, rows: 21, wall: 3, door: { x: 11 }, + windows: [{ x: 1, w: 3 }, { x: 6, w: 3 }], + board: { x: 9, w: 2 }, clock: { x: 5 }, + desks: [ + { x: 2, y: 6, seat: 'n' }, { x: 6, y: 6, seat: 'n' }, { x: 10, y: 6, seat: 'n' }, + { x: 2, y: 9, seat: 's' }, { x: 6, y: 9, seat: 's' }, { x: 10, y: 9, seat: 's' }, + { x: 2, y: 14, seat: 'n' }, { x: 6, y: 14, seat: 'n' }, { x: 10, y: 14, seat: 'n' } + ], + props: [ + { k: 'plant', x: 0, y: 3 }, { k: 'cooler', x: 13, y: 3 }, { k: 'counter', x: 0, y: 18, w: 3 }, { k: 'plant', x: 13, y: 12 }, + { k: 'rug', x: 4, y: 17, w: 6, h: 3 }, { k: 'sofa', x: 5, y: 20, w: 4 }, { k: 'printer', x: 13, y: 19 }, { k: 'plant', x: 0, y: 12 } + ], + errands: [ + { x: 12, y: 3, face: 'up', kind: 'cooler' }, { x: 1, y: 19, face: 'up', kind: 'coffee' }, { x: 12, y: 19, face: 'down', kind: 'printer' }, + { x: 7, y: 3, face: 'up', kind: 'window' }, { x: 6, y: 18, face: 'down', kind: 'rug' }, { x: 9, y: 3, face: 'up', kind: 'board' } + ], + ambient: { x: 12, y: 16 } + }, + strip: { + cols: 19, rows: 7, wall: 3, door: { x: 17 }, + windows: [{ x: 2, w: 3 }, { x: 9, w: 3 }], + board: { x: 13, w: 2 }, clock: { x: 6 }, + desks: [{ x: 1, y: 5, seat: 'n' }, { x: 4, y: 5, seat: 'n' }, { x: 7, y: 5, seat: 'n' }, { x: 10, y: 5, seat: 'n' }, { x: 13, y: 5, seat: 'n' }], + props: [{ k: 'plant', x: 16, y: 5 }, { k: 'plant', x: 0, y: 3 }, { k: 'cooler', x: 18, y: 5 }], + errands: [{ x: 17, y: 4, face: 'down', kind: 'cooler' }, { x: 10, y: 3, face: 'up', kind: 'window' }], + ambient: { x: 16, y: 6 } + }, + // one small office inside a sandbox: two rows of desks and a corner to wander to + box: { + cols: 16, rows: 10, wall: 3, door: { x: 13 }, + windows: [{ x: 1, w: 3 }, { x: 8, w: 3 }], + board: { x: 5, w: 2 }, clock: { x: 12 }, + desks: [{ x: 1, y: 5, seat: 'n' }, { x: 5, y: 5, seat: 'n' }, { x: 9, y: 5, seat: 'n' }, { x: 1, y: 8, seat: 'n' }, { x: 5, y: 8, seat: 'n' }], + props: [{ k: 'plant', x: 0, y: 3 }, { k: 'plant', x: 15, y: 3 }, { k: 'cooler', x: 15, y: 6 }, { k: 'bag', x: 10, y: 9 }, { k: 'bag', x: 12, y: 9 }], + errands: [{ x: 14, y: 6, face: 'down', kind: 'cooler' }, { x: 9, y: 3, face: 'up', kind: 'window' }, { x: 11, y: 8, face: 'down', kind: 'chat' }, { x: 13, y: 8, face: 'down', kind: 'chat' }], + ambient: { x: 13, y: 7 } + } + }; + + /* ---------- colours ---------- */ + var THEMES = { + office: { + name: 'The Office', floor: 'carpet', wall: 'office', desk: 'wood', + P: { floor: '#D6CDB8', floor2: '#CDC3AC', speck: '#C0B59D', wall: '#F4EEE1', wall2: '#E8E0CC', base: '#8E7B62', trim: '#FAF6EC', + top: '#CDA173', topHi: '#DDB98D', front: '#A37853', frontLo: '#83603F', chair: '#4B4F5E', chairHi: '#646979', + screen: '#D9EEF8', code: ['#4F9FD9', '#E060A8', '#3F8F5E', '#E0803A'], glow: 'rgba(170,215,255,', lamp: 'rgba(255,214,140,', + rug: '#FFC94F', rug2: '#F0B53A', leaf: ['#5DAA62', '#448A4C', '#7CC57F'], pot: '#B9683F', potHi: '#D07E52' } + } + }; + + /* ---------- floor painters ---------- */ + var FLOOR = { + carpet: function (g, L, P, rnd) { + var W = L.cols * T, H = L.rows * T, y0 = L.wall * T; + R(g, 0, y0, W, H - y0, P.floor); + for (var y = y0; y < H; y += T) for (var x = 0; x < W; x += T) { R(g, x, y, T, 1, P.floor2); R(g, x, y, 1, T, P.floor2); } + for (var i = 0; i < W * (H - y0) / 40; i++) R(g, Math.floor(rnd() * W), y0 + Math.floor(rnd() * (H - y0)), 1, 1, P.speck); + } + }; + + /* ---------- wall painters (static part; the sky and anything that moves is drawn per frame) ---------- */ + function windowFrame(g, x, y, w, h, frame, glass) { box(g, x, y, w, h, glass, OUT); R(g, x + 1, y + 1, w - 2, 1, frame); R(g, x + 1, y + h - 2, w - 2, 1, frame); R(g, x + 1, y + 1, 1, h - 2, frame); R(g, x + w - 2, y + 1, 1, h - 2, frame); R(g, x + (w >> 1), y + 1, 1, h - 2, frame); } + var WALL = { + office: function (g, L, P) { + var W = L.cols * T, H = L.wall * T; + R(g, 0, 0, W, H, P.wall); R(g, 0, 0, W, 4, P.wall2); R(g, 0, H - 4, W, 4, P.base); R(g, 0, H - 5, W, 1, P.trim); + L.windows.forEach(function (w) { windowFrame(g, w.x * T + 2, 7, w.w * T - 4, 26, '#F7F2E7', '#000'); }); + if (L.board) { box(g, L.board.x * T + 2, 8, L.board.w * T - 4, 24, '#FFFFFF', '#9C9789'); R(g, L.board.x * T + 6, 13, 14, 1, '#4F9FD9'); R(g, L.board.x * T + 6, 17, 22, 1, '#E060A8'); R(g, L.board.x * T + 6, 21, 10, 1, '#3F8F5E'); R(g, L.board.x * T + 22, 22, 8, 6, '#FFE08A'); } + if (L.clock) { var cx = L.clock.x * T + 8; box(g, cx - 5, 10, 11, 11, '#FFFFFF', OUT); R(g, cx, 12, 1, 4, OUT); R(g, cx, 15, 3, 1, OUT); } + var d = L.door.x * T; box(g, d + 1, 9, 2 * T - 2, H - 9, '#A98763', OUT); R(g, d + 3, 11, 2 * T - 6, H - 13, '#B9976F'); R(g, d + 2 * T - 7, 30, 2, 2, '#FFC94F'); + } + }; + + /* ---------- furniture ---------- */ + function drawPlant(g, x, y, P) { + box(g, x + 4, y + 2, 9, 7, P.pot, OUT); R(g, x + 5, y + 3, 7, 1, P.potHi); + R(g, x + 3, y - 7, 11, 9, P.leaf[1]); R(g, x + 5, y - 10, 7, 4, P.leaf[0]); R(g, x + 1, y - 4, 4, 4, P.leaf[0]); R(g, x + 12, y - 5, 4, 4, P.leaf[0]); R(g, x + 6, y - 9, 2, 2, P.leaf[2]); R(g, x + 10, y - 5, 2, 2, P.leaf[2]); R(g, x + 3, y - 2, 2, 1, P.leaf[2]); + } + function drawCooler(g, x, y, P) { + box(g, x + 3, y - 2, 11, 10, '#E9EEF2', OUT); R(g, x + 4, y - 1, 9, 2, '#FFFFFF'); box(g, x + 4, y - 13, 9, 11, '#9FD3F2', OUT); R(g, x + 5, y - 12, 2, 8, '#CFEAFA'); R(g, x + 6, y + 3, 3, 2, '#4F9FD9'); + } + function drawCounter(g, x, y, w, P) { + var W = w * T; + var top = '#F2EEE6'; + var front = '#C9BFAE'; + box(g, x, y - 2, W, 13, front, OUT); R(g, x + 1, y - 1, W - 2, 4, top); + for (var d = 0; d < w; d++) R(g, x + d * T + 7, y + 5, 2, 2, OUT); + box(g, x + 4, y - 11, 10, 10, '#3D3A44', OUT); R(g, x + 6, y - 9, 6, 2, '#E0803A'); R(g, x + 7, y - 4, 4, 2, '#FFFFFF'); + R(g, x + 20, y - 5, 4, 4, '#FFFFFF'); R(g, x + 20, y - 5, 4, 1, '#8E7B62'); R(g, x + 30, y - 6, 9, 5, '#E9EEF2'); + } + function drawRug(g, x, y, w, h, P) { var W = w * T, H = h * T; box(g, x + 2, y + 2, W - 4, H - 4, P.rug, P.rug2); for (var i = 6; i < W - 6; i += 6) R(g, x + i, y + 5, 3, H - 10, P.rug2); } + function drawSofa(g, x, y, w, P) { + var W = w * T, c = '#4F9FD9'; + var hi = '#7DB9E6'; + box(g, x, y - 4, W, 13, c, OUT); R(g, x + 1, y - 3, W - 2, 2, hi); box(g, x - 2, y - 2, 5, 11, c, OUT); box(g, x + W - 3, y - 2, 5, 11, c, OUT); R(g, x + 3, y + 3, W - 6, 1, OUT); + } + function drawTable(g, x, y, w, h, P) { + var W = w * T, top = '#E9E3D6'; + var edge = '#BEB5A3'; + for (var i = 0; i < w; i++) { box(g, x + i * T + 4, y - 6, 9, 7, P.chair, OUT); box(g, x + i * T + 4, y + h * T - 4, 9, 7, P.chair, OUT); } + box(g, x + 1, y, W - 2, h * T - 4, top, OUT); R(g, x + 2, y + h * T - 8, W - 4, 3, edge); + R(g, x + 10, y + 6, 8, 6, '#FFFFFF'); R(g, x + 26, y + 10, 6, 4, '#FFE08A'); R(g, x + W - 18, y + 5, 7, 9, '#2E2C36'); + } + function drawPrinter(g, x, y, P) { var c = '#E9EEF2'; box(g, x + 1, y - 6, 15, 14, c, OUT); R(g, x + 3, y - 9, 11, 4, '#FFFFFF'); R(g, x + 3, y - 3, 11, 2, OUT); R(g, x + 12, y + 2, 2, 2, '#3F8F5E'); } + function drawShelf(g, x, y, w, P) { + var W = w * T, wood = '#8C6A48'; + box(g, x + 1, y - 14, W - 2, 22, wood, OUT); R(g, x + 2, y - 6, W - 4, 1, OUT); + var cols = ['#4F9FD9', '#E060A8', '#FFC94F', '#3F8F5E', '#9482D3', '#E0803A']; + for (var i = 0; i < (W - 6) / 3; i++) { R(g, x + 3 + i * 3, y - 12, 2, 6, cols[i % 6]); R(g, x + 3 + i * 3, y - 4, 2, 6, cols[(i + 3) % 6]); } + } + function drawGlass(g, L) { + var G = L.glass; if (!G) return; + var x = G.x * T, y = G.y * T, w = G.w * T, h = G.h * T; + R(g, x, y, 2, h, 'rgba(160,210,235,.55)'); R(g, x, y + h - 2, w, 2, 'rgba(160,210,235,.55)'); + R(g, x + 2, y, 1, h, 'rgba(255,255,255,.7)'); + R(g, (G.gap) * T, y + h - 2, 2 * T, 2, 'rgba(0,0,0,0)'); + } + + /* desk: top surface, front panel, the monitor; seat 'n' shows the monitor's back, 's' its screen */ + function drawDesk(g, d, P, back) { + var x = d.x * T, y = d.y * T, W = 2 * T; + box(g, x, y - 2, W, 13, P.front, OUT); R(g, x + 1, y - 1, W - 2, 5, P.top); R(g, x + 1, y - 1, W - 2, 1, P.topHi); R(g, x + 1, y + 4, W - 2, 1, P.frontLo); R(g, x + 3, y + 6, W - 6, 1, P.frontLo); + // monitor + var mx = x + 10, my = y - 10; + if (back) { box(g, mx - 1, my, 14, 10, '#3A3843', OUT); R(g, mx + 5, my + 10, 3, 2, OUT); R(g, mx + 1, my + 2, 10, 1, '#4A4855'); } + else { box(g, mx - 1, my, 14, 10, '#2E2C36', OUT); R(g, mx + 5, my + 10, 3, 2, OUT); } + if (!back) { R(g, x + 4, y + 1, 7, 2, '#E9EEF2'); } + else { R(g, x + 3, y, 5, 3, '#FFFFFF'); } + var acc = (d.x * 7 + d.y * 3) % 5, ax = back ? x + 25 : x + 1; + if (acc === 0) { R(g, ax + 1, y - 5, 4, 4, '#5DAA62'); R(g, ax + 2, y - 7, 2, 2, '#7CC57F'); R(g, ax + 1, y - 1, 4, 3, '#B9683F'); } + else if (acc === 1) { R(g, ax + 2, y - 6, 1, 6, OUT); R(g, ax, y - 8, 5, 3, '#FFC94F'); } + else if (acc === 2) { R(g, ax, y - 1, 5, 3, '#FFFFFF'); R(g, ax, y - 2, 5, 1, '#E9E3D6'); } + else if (acc === 3) { R(g, ax + 1, y - 2, 3, 3, '#E060A8'); R(g, ax + 4, y - 1, 1, 1, '#E060A8'); } + else { R(g, ax, y - 4, 5, 4, '#2E2C36'); R(g, ax + 1, y - 3, 3, 2, '#9FD3F2'); } + } + function shadow(g, x, y, w) { g.fillStyle = 'rgba(30,20,10,.10)'; g.fillRect(x + 1, y, w - 1, 2); } + function drawArcade(g, x, y) { box(g, x + 1, y - 18, 15, 26, '#3A2F55', OUT); R(g, x + 3, y - 15, 11, 9, '#0B1020'); R(g, x + 4, y - 14, 3, 2, '#E060A8'); R(g, x + 9, y - 12, 3, 3, '#FFC94F'); R(g, x + 5, y - 9, 6, 1, '#3FB8C4'); R(g, x + 3, y - 4, 11, 3, '#55477A'); R(g, x + 5, y - 6, 1, 2, OUT); R(g, x + 4, y - 7, 3, 1, '#E060A8'); R(g, x + 10, y - 4, 2, 1, '#4ADE80'); R(g, x + 2, y - 19, 13, 3, '#E060A8'); } + function drawBag(g, x, y, c, hi) { R(g, x + 3, y - 1, 11, 9, OUT); R(g, x + 2, y + 1, 13, 6, OUT); R(g, x + 4, y, 9, 7, c); R(g, x + 3, y + 2, 11, 4, c); R(g, x + 5, y + 1, 4, 2, hi); } + function drawFiles(g, x, y) { var c = '#B8BEC6'; box(g, x + 1, y - 10, 14, 19, c, OUT); for (var i = 0; i < 3; i++) { R(g, x + 2, y - 9 + i * 6, 12, 1, 'rgba(0,0,0,.18)'); R(g, x + 6, y - 6 + i * 6, 4, 1, OUT); } } + function drawPoster(g, x, i) { var cols = [['#FFC94F', '#E060A8'], ['#4F9FD9', '#FFC94F'], ['#3F8F5E', '#E0803A']][i % 3]; box(g, x, 10, 14, 18, '#FFFFFF', OUT); R(g, x + 2, 12, 10, 8, cols[0]); R(g, x + 4, 14, 6, 4, cols[1]); R(g, x + 2, 22, 10, 1, '#6B685F'); R(g, x + 2, 24, 6, 1, '#6B685F'); } + function drawChairBehind(g, d, P) { var x = d.x * T + 10, y = d.y * T - 15; box(g, x, y, 12, 9, P.chair, OUT); R(g, x + 2, y + 1, 8, 2, P.chairHi); } + function drawChairFront(g, d, P) { var x = d.x * T + 10, y = d.y * T + 14; box(g, x, y, 12, 6, P.chair, OUT); R(g, x + 2, y + 1, 8, 1, P.chairHi); } + + /* ---------- the scene ---------- */ + function Scene(cv, opts) { + this.cv = cv; this.g = cv.getContext('2d'); + this.layoutName = opts.layout || 'wide'; + this.L = LAYOUTS[this.layoutName]; + this.W = this.L.cols * T; this.H = this.L.rows * T; + cv.width = this.W; cv.height = this.H; + this.themeName = opts.theme || 'office'; + this.time = opts.time == null ? 11 : opts.time; + this.fps = opts.fps || 30; + this.agents = []; + this.t = 0; this.raf = 0; this.running = false; this.last = 0; + this.rnd = mulberry(opts.seed || 7); + this.walk = this.buildWalk(); + this.bg = this.paint(this.themeName); + this.stars = []; var r = mulberry(99); for (var i = 0; i < 90; i++) this.stars.push([r() * this.W, r() * this.L.wall * T, r()]); + this.onTick = null; + var self = this; + (opts.agents || []).forEach(function (a) { self.add(a); }); + this.draw(); + } + Scene.prototype.buildWalk = function () { + var L = this.L, grid = []; + for (var y = 0; y < L.rows; y++) { grid.push([]); for (var x = 0; x < L.cols; x++) grid[y].push(y >= L.wall ? 1 : 0); } + function block(x, y, w, h) { for (var j = y; j < y + h; j++) for (var i = x; i < x + w; i++) if (grid[j] && grid[j][i] !== undefined) grid[j][i] = 0; } + L.desks.forEach(function (d) { block(d.x, d.y, 2, 1); }); + L.props.forEach(function (p) { if (p.k === 'rug') return; if (p.k === 'table') block(p.x, p.y, p.w, p.h); else block(p.x, p.y, p.w || 1, 1); }); + if (L.glass) { var G = L.glass; for (var j = G.y; j < G.y + G.h; j++) grid[j][G.x] = 0; for (var i = G.x; i < G.x + G.w; i++) if (i !== G.gap && i !== G.gap + 1) grid[G.y + G.h - 1][i] = 0; } + grid[L.wall - 1][L.door.x] = 1; grid[L.wall - 1][L.door.x + 1] = 1; + return grid; + }; + Scene.prototype.path = function (sx, sy, tx, ty) { + var L = this.L, grid = this.walk, W = L.cols, H = L.rows, prev = new Int16Array(W * H).fill(-1), q = [sy * W + sx], seen = new Uint8Array(W * H); + seen[sy * W + sx] = 1; + var ok = function (x, y) { return x >= 0 && y >= 0 && x < W && y < H && grid[y][x]; }; + while (q.length) { + var c = q.shift(), cx = c % W, cy = (c / W) | 0; + if (cx === tx && cy === ty) break; + var nb = [[1, 0], [-1, 0], [0, 1], [0, -1]]; + for (var k = 0; k < 4; k++) { var nx = cx + nb[k][0], ny = cy + nb[k][1], ni = ny * W + nx; if ((ok(nx, ny) || (nx === tx && ny === ty)) && !seen[ni]) { seen[ni] = 1; prev[ni] = c; q.push(ni); } } + } + var out = [], cur = ty * W + tx; if (!seen[cur]) return null; + while (cur !== sy * W + sx && cur >= 0) { out.unshift([cur % W, (cur / W) | 0]); cur = prev[cur]; } + return out; + }; + Scene.prototype.paint = function (name) { + var th = THEMES[name], P = th.P, L = this.L, c = canvas(this.W, this.H), g = c.getContext('2d'), rnd = mulberry(11); + FLOOR[th.floor](g, L, P, rnd); + WALL[th.wall](g, L, P); + var self = this; + L.props.forEach(function (p) { if (p.k === 'rug') drawRug(g, p.x * T, p.y * T, p.w, p.h, P); }); + drawGlassBase(g, L); + // things on the floor, painted top to bottom; desks are drawn per frame so seated agents sit behind them + var items = []; + L.props.forEach(function (p) { if (p.k !== 'rug') items.push({ y: p.y + (p.h || 1), p: p }); }); + items.sort(function (a, b) { return a.y - b.y; }); + items.forEach(function (it) { + var p = it.p, x = p.x * T, y = p.y * T; + if (p.k === 'plant') drawPlant(g, x, y + 6, P); + else if (p.k === 'cooler') drawCooler(g, x, y + 7, P); + else if (p.k === 'counter') drawCounter(g, x, y + 2, p.w, P); + else if (p.k === 'sofa') drawSofa(g, x, y + 4, p.w, P); + else if (p.k === 'table') drawTable(g, x, y + 2, p.w, p.h, P); + else if (p.k === 'printer') drawPrinter(g, x, y + 7, P); + else if (p.k === 'shelf') drawShelf(g, x, y + 6, p.w, P); + else if (p.k === 'arcade') drawArcade(g, x, y + 7); + else if (p.k === 'bag') drawBag(g, x, y + 6, '#E060A8', '#F2A6CF'); + else if (p.k === 'files') drawFiles(g, x, y + 7); + }); + if (L.posters) L.posters.forEach(function (px, i) { drawPoster(g, Math.round(px * T), i); }); + L.desks.forEach(function (d) { shadow(g, d.x * T, d.y * T + 11, 2 * T); }); + return c; + }; + function drawGlassBase(g, L) { if (!L.glass) return; var G = L.glass; R(g, G.x * T + 2, G.y * T, G.w * T - 2, G.h * T - 2, 'rgba(255,255,255,.18)'); } + + /* agents */ + Scene.prototype.seatTile = function (d) { return d.seat === 'n' ? [d.x, d.y - 1] : [d.x, d.y + 1]; }; + Scene.prototype.add = function (a, o) { + o = o || {}; + var L = this.L, d = L.desks[a.desk], st = this.seatTile(d); + var ag = { id: a.id, name: a.name, recipe: a.recipe, desk: a.desk, spr: sprites(a.recipe), x: st[0], y: st[1], fx: st[0] * T + 16, fy: 0, state: 'seat', face: d.seat === 'n' ? 'down' : 'up', phase: 0, timer: 2 + this.rnd() * 6, path: null, bubble: null, bob: 0, typing: this.rnd(), busy: a.busy !== false, hidden: false, human: !!a.human }; + ag.fy = this.seatFeetY(d); + if (o.enter) { ag.x = L.door.x; ag.y = L.wall - 1; ag.fx = ag.x * T + 8; ag.fy = ag.y * T + 14; ag.state = 'walk'; ag.path = this.path(ag.x, ag.y, st[0], st[1]); ag.then = 'seat'; ag.face = 'down'; } + this.agents.push(ag); + return ag; + }; + Scene.prototype.seatFeetY = function (d) { return d.seat === 'n' ? d.y * T + 4 : d.y * T + 30; }; + Scene.prototype.get = function (id) { for (var i = 0; i < this.agents.length; i++) if (this.agents[i].id === id) return this.agents[i]; return null; }; + Scene.prototype.leave = function (id) { var a = this.get(id); if (!a) return; a.state = 'walk'; a.path = this.path(a.x, a.y, this.L.door.x, this.L.wall - 1); a.then = 'gone'; }; + Scene.prototype.come = function (id) { var a = this.get(id); if (!a || !a.hidden) return; a.hidden = false; a.x = this.L.door.x; a.y = this.L.wall - 1; a.fx = a.x * T + 8; a.fy = a.y * T + 14; var d = this.L.desks[a.desk], st = this.seatTile(d); a.state = 'walk'; a.path = this.path(a.x, a.y, st[0], st[1]); a.then = 'seat'; }; + Scene.prototype.grab = function (id) { var a = this.get(id); if (!a || a.hidden) return null; a.state = 'held'; a.path = null; a.target = null; a.bubble = null; return a; }; + Scene.prototype.hold = function (id, x, y) { var a = this.get(id); if (a && a.state === 'held') { a.fx = x; a.fy = y; } }; + Scene.prototype.drop = function (id) { + var a = this.get(id); if (!a || a.state !== 'held') return; + var L = this.L, w = this.walk, tx = Math.max(0, Math.min(L.cols - 1, Math.round((a.fx - 8) / T))), ty = Math.max(L.wall, Math.min(L.rows - 1, Math.round((a.fy - 14) / T))); + if (!w[ty][tx]) { var best = null, bd = 1e9; for (var y = L.wall; y < L.rows; y++) for (var x = 0; x < L.cols; x++) if (w[y][x]) { var dd = (x - tx) * (x - tx) + (y - ty) * (y - ty); if (dd < bd) { bd = dd; best = [x, y]; } } if (best) { tx = best[0]; ty = best[1]; } } + a.x = tx; a.y = ty; a.fx = tx * T + 8; a.fy = ty * T + 14; + var st = this.seatTile(L.desks[a.desk]); a.state = 'walk'; a.path = this.path(tx, ty, st[0], st[1]) || []; a.then = 'seat'; + }; + Scene.prototype.bubble = function (id, kind, secs) { var a = this.get(id); if (a) a.bubble = { kind: kind, until: this.t + (secs || 3) }; }; + Scene.prototype.feet = function (id) { var a = this.get(id); return a ? { x: a.fx, y: a.fy, head: a.fy - (a.state === 'seat' && this.L.desks[a.desk].seat === 'n' ? 28 : 31) } : null; }; + Scene.prototype.hit = function (px, py) { + for (var i = this.agents.length - 1; i >= 0; i--) { var a = this.agents[i]; if (a.hidden) continue; var top = a.fy - 31; if (px >= a.fx - 9 && px <= a.fx + 9 && py >= top && py <= a.fy) return a.id; } + return null; + }; + Scene.prototype.errand = function (a) { + var E = this.L.errands, e = E[Math.floor(this.rnd() * E.length)]; + var taken = this.agents.some(function (b) { return b !== a && b.target === e; }); if (taken) return false; + var p = this.path(a.x, a.y, e.x, e.y); if (!p || !p.length) return false; + a.state = 'walk'; a.path = p; a.then = 'errand'; a.target = e; return true; + }; + Scene.prototype.step = function (dt) { + var self = this, L = this.L; + this.agents.forEach(function (a) { + if (a.hidden) return; + if (a.state === 'seat') { + a.typing += dt; a.timer -= dt; + if (a.timer <= 0) { if (a.busy && self.rnd() < .55 && self.errand(a)) { a.timer = 0; } else a.timer = 3 + self.rnd() * 7; } + } else if (a.state === 'walk') { + if (!a.path || !a.path.length) { self.arrive(a); return; } + var n = a.path[0], tx = n[0] * T + 8, ty = n[1] * T + 14, sp = 34 * dt; + var dx = tx - a.fx, dy = ty - a.fy, dist = Math.hypot(dx, dy); + a.face = Math.abs(dy) > Math.abs(dx) ? (dy < 0 ? 'up' : 'down') : (a.face === 'up' && Math.abs(dy) > .5 ? 'up' : 'down'); + if (dist <= sp) { a.fx = tx; a.fy = ty; a.x = n[0]; a.y = n[1]; a.path.shift(); if (!a.path.length) self.arrive(a); } + else { a.fx += dx / dist * sp; a.fy += dy / dist * sp; } + a.phase += dt * 7; + } else if (a.state === 'held') { + a.phase += dt * 9; + } else if (a.state === 'errand') { + a.timer -= dt; if (a.timer <= 0) { var d = L.desks[a.desk], st = self.seatTile(d); a.target = null; a.state = 'walk'; a.path = self.path(a.x, a.y, st[0], st[1]); a.then = 'seat'; } + } + }); + }; + Scene.prototype.arrive = function (a) { + var L = this.L; + if (a.then === 'seat') { var d = L.desks[a.desk]; a.state = 'seat'; a.fx = d.x * T + 16; a.fy = this.seatFeetY(d); a.face = d.seat === 'n' ? 'down' : 'up'; a.timer = 4 + this.rnd() * 8; } + else if (a.then === 'errand') { a.state = 'errand'; a.face = a.target.face; a.timer = 2 + this.rnd() * 3; var k = a.target.kind; a.bubble = { kind: k === 'coffee' ? 'coffee' : k === 'chat' ? 'dots' : k === 'window' ? 'sun' : k === 'board' ? 'idea' : k === 'cooler' ? 'drop' : 'dots', until: this.t + 2.4 }; } + else if (a.then === 'gone') { a.hidden = true; a.state = 'gone'; } + }; + + /* drawing */ + function drawBubble(g, x, y, kind, t) { + box(g, x - 7, y - 11, 15, 10, '#FFFFFF', OUT); R(g, x - 1, y - 2, 3, 2, '#FFFFFF'); R(g, x - 1, y - 1, 1, 2, OUT); R(g, x + 1, y - 1, 1, 1, OUT); R(g, x, y, 1, 1, OUT); + var cx = x - 4, cy = y - 8; + if (kind === 'dots') { for (var i = 0; i < 3; i++) R(g, cx + 1 + i * 3, cy + 2 + ((Math.floor(t * 4) + i) % 3 === 0 ? -1 : 0), 2, 2, '#6B685F'); } + else if (kind === 'coffee') { R(g, cx + 1, cy + 1, 6, 5, '#8C5A31'); R(g, cx + 7, cy + 2, 1, 2, '#8C5A31'); R(g, cx + 2, cy + 1, 4, 1, '#D9B184'); } + else if (kind === 'check') { R(g, cx + 1, cy + 3, 2, 2, '#3F8F5E'); R(g, cx + 3, cy + 4, 2, 2, '#3F8F5E'); R(g, cx + 5, cy + 1, 2, 3, '#3F8F5E'); R(g, cx + 7, cy, 1, 2, '#3F8F5E'); } + else if (kind === 'ask') { R(g, cx + 3, cy, 2, 4, '#D6903F'); R(g, cx + 3, cy + 5, 2, 1, '#D6903F'); } + else if (kind === 'heart') { R(g, cx + 1, cy + 1, 2, 2, '#E060A8'); R(g, cx + 5, cy + 1, 2, 2, '#E060A8'); R(g, cx + 1, cy + 2, 6, 2, '#E060A8'); R(g, cx + 2, cy + 4, 4, 1, '#E060A8'); R(g, cx + 3, cy + 5, 2, 1, '#E060A8'); } + else if (kind === 'idea') { R(g, cx + 2, cy, 4, 4, '#FFC94F'); R(g, cx + 3, cy + 4, 2, 2, '#8E8A84'); } + else if (kind === 'sun') { R(g, cx + 2, cy + 1, 4, 4, '#FFC94F'); R(g, cx + 3, cy, 2, 1, '#FFC94F'); R(g, cx + 3, cy + 5, 2, 1, '#FFC94F'); R(g, cx + 1, cy + 2, 1, 2, '#FFC94F'); R(g, cx + 6, cy + 2, 1, 2, '#FFC94F'); } + else if (kind === 'drop') { R(g, cx + 3, cy, 2, 2, '#4F9FD9'); R(g, cx + 2, cy + 2, 4, 3, '#4F9FD9'); R(g, cx + 3, cy + 3, 1, 1, '#CFEAFA'); } + else if (kind === 'zz') { R(g, cx + 1, cy + 1, 4, 1, '#9482D3'); R(g, cx + 3, cy + 2, 1, 1, '#9482D3'); R(g, cx + 2, cy + 3, 1, 1, '#9482D3'); R(g, cx + 1, cy + 4, 4, 1, '#9482D3'); R(g, cx + 6, cy + 3, 2, 1, '#9482D3'); R(g, cx + 6, cy + 5, 2, 1, '#9482D3'); } + else if (kind === 'mail') { R(g, cx, cy + 1, 9, 6, '#FFFFFF'); R(g, cx, cy + 1, 9, 1, '#E0803A'); R(g, cx, cy + 6, 9, 1, '#E0803A'); R(g, cx, cy + 1, 1, 6, '#E0803A'); R(g, cx + 8, cy + 1, 1, 6, '#E0803A'); R(g, cx + 2, cy + 2, 2, 1, '#E0803A'); R(g, cx + 5, cy + 2, 2, 1, '#E0803A'); R(g, cx + 4, cy + 3, 1, 1, '#E0803A'); } + } + function skyAt(h) { + var K = [[0, '#141A38'], [5, '#1C2550'], [6.5, '#F2A87E'], [8, '#A6D8F2'], [16.5, '#A6D8F2'], [18.5, '#F29E6C'], [20, '#2A3366'], [24, '#141A38']]; + for (var i = 0; i < K.length - 1; i++) if (h >= K[i][0] && h <= K[i + 1][0]) return mix(K[i][1], K[i + 1][1], (h - K[i][0]) / (K[i + 1][0] - K[i][0])); + return '#A6D8F2'; + } + function darkAt(h) { if (h >= 8 && h <= 17) return 0; if (h > 17 && h < 20.5) return (h - 17) / 3.5 * .58; if (h >= 20.5 || h < 5) return .58; return .58 * (1 - (h - 5) / 3); } + + Scene.prototype.drawAgent = function (g, a, crop) { + if (a.state === 'held') { + g.fillStyle = 'rgba(20,20,30,.22)'; g.fillRect(Math.round(a.fx - 6), Math.round(a.fy + 2), 12, 2); + g.drawImage(a.spr.front[Math.floor(a.phase) % 2 ? 1 : 2], Math.round(a.fx - 9), Math.round(a.fy - 38)); return; + } + var fr = a.face === 'up' ? a.spr.back : a.spr.front, ph = a.state === 'walk' ? (Math.floor(a.phase) % 4 === 1 ? 1 : Math.floor(a.phase) % 4 === 3 ? 2 : 0) : 0; + var img = fr[ph], x = Math.round(a.fx - 9), y = Math.round(a.fy - 31); + if (a.state === 'seat' && a.busy) { var tb = Math.sin(a.typing * 9) > .6 ? 1 : 0; y += tb; } + if (crop) g.drawImage(img, 0, 0, 18, crop, x, y, 18, crop); else g.drawImage(img, x, y); + }; + Scene.prototype.draw = function () { + var g = this.g, L = this.L, th = THEMES[this.themeName], P = th.P, t = this.t, self = this; + g.imageSmoothingEnabled = false; + // sky and anything behind the windows + var sky = skyAt(this.time), night = darkAt(this.time); + g.drawImage(this.bg, 0, 0); + this.drawWindows(g, sky, night); + // desks and agents, sorted so the seated sit behind their desks + var list = []; + L.desks.forEach(function (d, i) { list.push({ z: d.y * T + 8, desk: d }); }); + this.agents.forEach(function (a) { if (!a.hidden) list.push({ z: a.state === 'held' ? 1e9 : a.state === 'seat' ? (L.desks[a.desk].seat === 'n' ? L.desks[a.desk].y * T - 1 : L.desks[a.desk].y * T + 17) : a.fy, agent: a }); }); + list.sort(function (p, q) { return p.z - q.z; }); + list.forEach(function (it) { + if (it.desk) { + var d = it.desk, seated = self.agents.some(function (a) { return !a.hidden && a.state === 'seat' && L.desks[a.desk] === d; }); + if (d.seat === 'n' && !seated) drawChairBehind(g, d, P); + drawDesk(g, d, P, d.seat === 'n'); + if (d.seat === 's') { self.drawScreen(g, d, P, seated, t); if (!seated) drawChairFront(g, d, P); } + } else { + var a = it.agent; + if (a.state === 'seat') { var d2 = L.desks[a.desk]; if (d2.seat === 'n') { drawChairBehind(g, d2, P); self.drawAgent(g, a); } else { drawChairFront(g, d2, P); self.drawAgent(g, a, 24); } } + else self.drawAgent(g, a); + } + }); + if (L.glass) drawGlass(g, L); + // night + if (night > 0) { + g.fillStyle = 'rgba(14,16,40,' + night + ')'; g.fillRect(0, 0, this.W, this.H); + g.globalCompositeOperation = 'lighter'; + this.agents.forEach(function (a) { + if (a.hidden || a.state !== 'seat') return; var d = L.desks[a.desk]; + var cx = d.x * T + 16, cy = d.y * T - 4, rg = g.createRadialGradient(cx, cy, 2, cx, cy, 26); + rg.addColorStop(0, P.glow + (night * .9) + ')'); rg.addColorStop(1, P.glow + '0)'); g.fillStyle = rg; g.fillRect(cx - 28, cy - 28, 56, 56); + }); + if (L.ambient) { var lx = L.ambient.x * T, ly = L.ambient.y * T, lg = g.createRadialGradient(lx, ly, 2, lx, ly, 40); lg.addColorStop(0, P.lamp + (night * .7) + ')'); lg.addColorStop(1, P.lamp + '0)'); g.fillStyle = lg; g.fillRect(lx - 42, ly - 42, 84, 84); } + g.globalCompositeOperation = 'source-over'; + this.agents.forEach(function (a) { if (a.hidden || a.state !== 'seat') return; var d = L.desks[a.desk]; if (d.seat === 's') self.drawScreen(g, d, P, true, t); }); + } + // bubbles last + this.agents.forEach(function (a) { if (a.hidden || !a.bubble || a.bubble.until < t) return; var head = a.fy - (a.state === 'seat' && L.desks[a.desk].seat === 's' ? 24 : 31); drawBubble(g, Math.round(a.fx), Math.round(head - 1), a.bubble.kind, t); }); + if (this.onDraw) this.onDraw(g); + }; + Scene.prototype.drawScreen = function (g, d, P, on, t) { + var mx = d.x * T + 10, my = d.y * T - 10; + R(g, mx, my + 1, 12, 8, on ? P.screen : '#24222B'); + if (on) { for (var i = 0; i < 3; i++) { var w = 3 + ((Math.floor(t * 2.5) + i * 3 + d.x) % 7); R(g, mx + 1, my + 2 + i * 2, w, 1, P.code[(i + d.x) % 4]); } } + }; + Scene.prototype.drawWindows = function (g, sky, night) { + var L = this.L, t = this.t, self = this; + L.windows.forEach(function (w) { + var x0 = w.x * T + 3, ww = w.w * T - 6, y0 = 8, hh = 24; + g.fillStyle = sky; g.fillRect(x0, y0, ww, hh); + if (night > .25) self.stars.forEach(function (s) { var sx = s[0] % ww, sy = s[1] % hh; g.fillStyle = 'rgba(255,255,255,' + (.25 + .6 * Math.abs(Math.sin(t * .8 + s[2] * 7))) + ')'; g.fillRect(x0 + (sx | 0), y0 + (sy | 0), 1, 1); }); + else { var cx = ((t * 3 + w.x * 20) % (ww + 20)) - 10; g.fillStyle = 'rgba(255,255,255,.9)'; g.fillRect(x0 + (cx | 0), y0 + 7, 8, 2); g.fillRect(x0 + (cx | 0) + 2, y0 + 5, 4, 2); } + g.fillStyle = 'rgba(0,0,0,.08)'; g.fillRect(x0, y0 + hh - 6, ww, 6); + g.fillStyle = '#F7F2E7'; g.fillRect(x0 + (ww >> 1), y0, 1, hh); + }); + if (L.clock) { var cx2 = L.clock.x * T + 8, ang = (this.time / 12) * Math.PI * 2; g.fillStyle = '#E060A8'; g.fillRect(cx2 + Math.round(Math.sin(ang) * 3), 15 - Math.round(Math.cos(ang) * 3), 1, 1); } + }; + + Scene.prototype.setTime = function (h) { this.time = ((h % 24) + 24) % 24; if (!this.running) this.draw(); }; + Scene.prototype.frame = function (now) { + if (!this.running) return; + var self = this; + this.raf = requestAnimationFrame(function (n) { self.frame(n); }); + if (now - this.last < 1000 / this.fps - 2) return; + var dt = Math.min(.1, (now - (this.last || now)) / 1000); this.last = now; this.t += dt; + this.step(dt); this.draw(); if (this.onTick) this.onTick(dt); + }; + Scene.prototype.start = function () { if (this.running) return; this.running = true; this.last = 0; var self = this; this.raf = requestAnimationFrame(function (n) { self.last = n; self.frame(n); }); }; + Scene.prototype.stop = function () { this.running = false; cancelAnimationFrame(this.raf); }; + + window.MDOffice = { Scene: Scene, THEMES: THEMES, LAYOUTS: LAYOUTS, portrait: portrait, sprites: sprites, recipeOf: recipeOf, T: T }; +})(); diff --git a/docs/assets/sims/cyber-dunk.jpg b/docs/assets/sims/cyber-dunk.jpg new file mode 100644 index 0000000000..81bdbc21be Binary files /dev/null and b/docs/assets/sims/cyber-dunk.jpg differ diff --git a/docs/assets/sims/cyber-dunk.mp4 b/docs/assets/sims/cyber-dunk.mp4 new file mode 100644 index 0000000000..7ef6fb0612 Binary files /dev/null and b/docs/assets/sims/cyber-dunk.mp4 differ diff --git a/docs/assets/sims/hurry-porter.jpg b/docs/assets/sims/hurry-porter.jpg new file mode 100644 index 0000000000..b83a8df720 Binary files /dev/null and b/docs/assets/sims/hurry-porter.jpg differ diff --git a/docs/assets/sims/hurry-porter.mp4 b/docs/assets/sims/hurry-porter.mp4 new file mode 100644 index 0000000000..7b2a980cd6 Binary files /dev/null and b/docs/assets/sims/hurry-porter.mp4 differ diff --git a/docs/assets/sims/off-white-house.jpg b/docs/assets/sims/off-white-house.jpg new file mode 100644 index 0000000000..ae408814be Binary files /dev/null and b/docs/assets/sims/off-white-house.jpg differ diff --git a/docs/assets/sims/off-white-house.mp4 b/docs/assets/sims/off-white-house.mp4 new file mode 100644 index 0000000000..4c7223c7be Binary files /dev/null and b/docs/assets/sims/off-white-house.mp4 differ diff --git a/docs/assets/sims/one-fragment.jpg b/docs/assets/sims/one-fragment.jpg new file mode 100644 index 0000000000..80ed9732a4 Binary files /dev/null and b/docs/assets/sims/one-fragment.jpg differ diff --git a/docs/assets/sims/one-fragment.mp4 b/docs/assets/sims/one-fragment.mp4 new file mode 100644 index 0000000000..ba635c6853 Binary files /dev/null and b/docs/assets/sims/one-fragment.mp4 differ diff --git a/docs/assets/sims/south-of-market.jpg b/docs/assets/sims/south-of-market.jpg new file mode 100644 index 0000000000..8b17f34e7b Binary files /dev/null and b/docs/assets/sims/south-of-market.jpg differ diff --git a/docs/assets/sims/south-of-market.mp4 b/docs/assets/sims/south-of-market.mp4 new file mode 100644 index 0000000000..d2a7c22850 Binary files /dev/null and b/docs/assets/sims/south-of-market.mp4 differ diff --git a/docs/assets/sims/toy-tale.jpg b/docs/assets/sims/toy-tale.jpg new file mode 100644 index 0000000000..2c5d0dcdb6 Binary files /dev/null and b/docs/assets/sims/toy-tale.jpg differ diff --git a/docs/assets/sims/toy-tale.mp4 b/docs/assets/sims/toy-tale.mp4 new file mode 100644 index 0000000000..e010d2f2ce Binary files /dev/null and b/docs/assets/sims/toy-tale.mp4 differ diff --git a/docs/assets/site.js b/docs/assets/site.js new file mode 100644 index 0000000000..de8af697bc --- /dev/null +++ b/docs/assets/site.js @@ -0,0 +1,910 @@ +/* Everything that moves on the landing page. No framework: one scroll loop, IntersectionObservers that + start and stop each scene, and small interactions on plain elements. */ +(function () { + 'use strict'; + var $ = function (s, r) { return (r || document).querySelector(s); }; + var $$ = function (s, r) { return Array.prototype.slice.call((r || document).querySelectorAll(s)); }; + var O = window.MDOffice, A = window.MDAv, S = window.MDStapler; + if (!O || !A || !S) return; + var RM = matchMedia('(prefers-reduced-motion: reduce)').matches; + var SMALL = matchMedia('(max-width: 700px)').matches; + var HOVER = matchMedia('(hover: hover) and (pointer: fine)').matches; + var ua = navigator.userAgent; + var isIOS = /iPhone|iPad|iPod/.test(ua) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1); + var isPhone = isIOS || /Android/.test(ua); + var OS = isPhone ? 'phone' : /Win/.test(navigator.platform || ua) ? 'win' : /Linux|X11|CrOS/.test(ua) ? 'linux' : 'mac'; + var clamp = function (v, a, b) { return v < a ? a : v > b ? b : v; }; + var lerp = function (a, b, t) { return a + (b - a) * t; }; + var wait = function (ms) { return new Promise(function (r) { setTimeout(r, ms); }); }; + function el(tag, cls, html) { var e = document.createElement(tag); if (cls) e.className = cls; if (html != null) e.innerHTML = html; return e; } + + /* ---------- vendor marks (assets/brands.js) ---------- */ + var BR = window.MDBrands || { engines: {}, apps: {}, mini: {} }, uidN = 0; + var ENGKEY = { 'Claude Code': 'claude', 'Codex': 'codex', 'Gemini CLI': 'gemini', 'Grok': 'grok', 'Cursor': 'cursor', 'Copilot CLI': 'copilot', 'Qwen': 'qwen', 'Kimi Code': 'kimi', 'OpenCode': 'opencode', 'Crush': 'crush', 'Pi': 'pi', 'Antigravity': 'antigravity' }; + // each copy of a mark gets its own ids, so a gradient never points at a copy that is hidden + function uniq(svg) { var n = ++uidN, ids = []; svg.replace(/id="([^"]+)"/g, function (m, id) { ids.push(id); }); ids.forEach(function (id) { svg = svg.split(id).join(id + '_' + n); }); return svg; } + function mark(key) { var e = BR.engines[key]; return e ? '' + uniq(e.logo) + '' : ''; } + function engHTML(name) { return mark(ENGKEY[name]) + name; } + function appLogo(key) { var a = BR.apps[key]; return a ? uniq(a.logo) : ''; } + + /* ---------- the cast ---------- */ + var CAST = { + pam: ['Pam', 'Product designer', 'Claude Code', '#F7E8F0'], + kelly: ['Kelly', 'Customer support', 'Codex', '#FDEAF4'], + dwight: ['Dwight', 'Sales development', 'Gemini CLI', '#F8F5E5'], + kevin: ['Kevin', 'QA and test engineer', 'Claude Code', '#E6EFF6'], + angela: ['Angela', 'Bookkeeper', 'Codex', '#EEECF5'], + oscar: ['Oscar', 'Research analyst', 'Grok', '#E7F0EB'], + andy: ['Andy', 'Growth marketer', 'Cursor', '#F4E9E0'], + jim: ['Jim', 'Account executive', 'Claude Code', '#E6EFF6'], + ryan: ['Ryan', 'Orchestrator', 'Claude Code', '#FFF3D2'], + stanley: ['Stanley', 'Operations', 'Qwen', '#F4E9E0'], + phyllis: ['Phyllis', 'Social media manager', 'Kimi Code', '#EEECF5'], + meredith: ['Meredith', 'Community manager', 'OpenCode', '#E7F0EB'], + toby: ['Toby', 'HR and recruiting', 'Copilot CLI', '#ECEAE3'], + creed: ['Creed', 'Security engineer', 'Codex', '#ECEAE3'] + }; + var WIDE_TEAM = ['pam', 'kelly', 'dwight', 'kevin', 'angela', 'oscar', 'andy', 'jim', 'ryan', 'stanley', 'phyllis', 'meredith', 'toby']; + var TALL_TEAM = ['pam', 'kelly', 'dwight', 'kevin', 'angela', 'oscar', 'andy', 'jim', 'ryan']; + function rec(name) { return O.recipeOf(name); } + function headCanvas(recipe) { + var buf = A.portraitBufFor(recipe), c = document.createElement('canvas'); c.width = 18; c.height = 20; + var g = c.getContext('2d'), im = g.createImageData(18, 20); im.data.set(buf.subarray(0, 18 * 20 * 4)); g.putImageData(im, 0, 0); + c.className = 'pxa'; return c; + } + function tile(recipe, tint) { var t = el('span', 'tile'); t.style.background = tint || '#ECEAE3'; t.appendChild(headCanvas(recipe)); return t; } + function team(names, layout) { + var n = O.LAYOUTS[layout].desks.length; + return names.slice(0, n).map(function (nm, i) { return { id: nm, name: CAST[nm][0], recipe: rec(nm), desk: i }; }); + } + function stLabel(k) { return { 'st-work': 'Working', 'st-ask': 'Asks you', 'st-think': 'Thinking', 'st-idle': 'Idle', 'st-queue': 'Queued' }[k] || 'Idle'; } + function agentCard(nm, status) { + var c = CAST[nm], d = el('div', 'acard-in'); d.style.cssText = 'display:contents'; + var who = el('div', 'who', '
' + c[0] + '
' + c[1] + '' + engHTML(c[2]) + '
'); + var st = el('span', 'st ' + (status || 'st-idle'), '' + stLabel(status || 'st-idle')); + who.firstChild.appendChild(st); + d.appendChild(tile(rec(nm), c[3])); d.appendChild(who); + return { root: d, st: st }; + } + function setSt(st, kind) { st.className = 'st ' + kind; st.innerHTML = '' + stLabel(kind); } + + /* ---------- visibility helper: run a scene only while it is on screen ---------- */ + function whenVisible(node, on, off, margin) { + var io = new IntersectionObserver(function (es) { es.forEach(function (e) { if (e.isIntersecting) on(); else if (off) off(); }); }, { rootMargin: margin || '120px 0px' }); + io.observe(node); return io; + } + function scale(canvas) { return canvas.getBoundingClientRect().width / canvas.width; } + + (function stars() { + var box = $('#ghStars'); if (!box) return; + function short(n) { return n < 1000 ? String(n) : n < 100000 ? (Math.floor(n / 100) / 10).toFixed(1).replace(/\.0$/, '') + 'k' : Math.floor(n / 1000) + 'k'; } + function show(n) { if (typeof n !== 'number' || !(n >= 0)) return; box.querySelector('b').textContent = short(n); box.hidden = false; box.parentNode.setAttribute('aria-label', 'Munder Difflin on GitHub, ' + n.toLocaleString('en') + ' stars'); + document.querySelectorAll('[data-stars]').forEach(function (b) { b.textContent = short(n); }); + document.querySelectorAll('[data-stars-label]').forEach(function (l) { l.textContent = 'stars on GitHub'; }); } + var KEY = 'md-gh-stars', c = null; + try { c = JSON.parse(sessionStorage.getItem(KEY) || 'null'); } catch (e) { } + if (c && Date.now() - c.t < 30 * 60 * 1000) return show(c.n); + if (!window.fetch) return; + fetch('https://api.github.com/repos/chaitanyagiri/munder-difflin', { headers: { Accept: 'application/vnd.github+json' } }) + .then(function (r) { return r.ok ? r.json() : null; }) + .then(function (j) { if (!j || typeof j.stargazers_count !== 'number') return; show(j.stargazers_count); try { sessionStorage.setItem(KEY, JSON.stringify({ n: j.stargazers_count, t: Date.now() })); } catch (e) { } }) + .catch(function () { }); + })(); + $$('.lim[data-mark]').forEach(function (l) { l.insertAdjacentHTML('afterbegin', mark(l.dataset.mark)); }); + (function () { + var a = $('#phBadge'), im = a && a.querySelector('img'); if (!im) return; + function fallback() { a.innerHTML = ''; a.setAttribute('aria-label', 'Number 5 Product of the Day on Product Hunt'); } + if (im.complete && !im.naturalWidth) fallback(); else im.addEventListener('error', fallback); + })(); + + /* ---------- reveal ---------- */ + var rio = new IntersectionObserver(function (es) { es.forEach(function (e) { if (e.isIntersecting) { e.target.classList.add('in'); rio.unobserve(e.target); } }); }, { rootMargin: '0px 0px -8% 0px' }); + $$('[data-r]').forEach(function (n) { rio.observe(n); }); + + /* ---------- downloads and Send to my computer ---------- */ + var DL = { mac: 'Download for Mac', win: 'Download for Windows', linux: 'Download for Linux', phone: 'Send to my computer' }; + $$('[data-dl-label]').forEach(function (s) { s.textContent = s.closest('.nav') ? (OS === 'phone' ? 'Get it' : 'Download') : DL[OS]; }); + var sheet = $('#sendSheet'); + function openSend() { + var data = { title: 'Munder Difflin', text: 'Download Munder Difflin on my computer', url: 'https://harnessmd.com/download' }; + if (navigator.share) { navigator.share(data).catch(function (e) { if (!e || e.name !== 'AbortError') sheet.classList.add('on'); }); } + else sheet.classList.add('on'); + } + if (OS === 'phone') { + $$('[data-dl]').forEach(function (a) { a.addEventListener('click', function (e) { e.preventDefault(); openSend(); }); }); + $$('[data-os]').forEach(function (a) { a.hidden = true; }); + } + $$('[data-send]').forEach(function (b) { if (OS === 'phone') b.hidden = false; b.addEventListener('click', openSend); }); + $('#sendClose').addEventListener('click', function () { sheet.classList.remove('on'); }); + sheet.addEventListener('click', function (e) { if (e.target === sheet) sheet.classList.remove('on'); }); + $('#copyLink').addEventListener('click', function () { + var b = this; var done = function () { b.textContent = 'Copied. Paste it on your computer.'; setTimeout(function () { b.textContent = 'Copy the link'; }, 2400); }; + if (navigator.clipboard) navigator.clipboard.writeText('https://harnessmd.com/download').then(done, done); else done(); + }); + + /* ---------- magnetic buttons ---------- */ + if (HOVER && !RM) $$('[data-mag]').forEach(function (b) { + b.addEventListener('pointermove', function (e) { var r = b.getBoundingClientRect(); b.style.transform = 'translate(' + ((e.clientX - r.left - r.width / 2) * .16).toFixed(1) + 'px,' + ((e.clientY - r.top - r.height / 2) * .24).toFixed(1) + 'px)'; }); + b.addEventListener('pointerleave', function () { b.style.transform = ''; }); + }); + + /* ---------- nav, progress and the little walker ---------- */ + var nav = $('#nav'), fill = $('#progFill'), walker = $('#walker'), wcv = document.createElement('canvas'); + wcv.width = 18; wcv.height = 32; wcv.className = 'pxa'; walker.appendChild(wcv); + var wspr = O.sprites(rec('jim')), wg = wcv.getContext('2d'), lastY = 0, walked = 0; + function drawWalker(ph) { wg.clearRect(0, 0, 18, 32); wg.drawImage(wspr.front[ph], 0, 0); } + drawWalker(0); + + /* ---------- dark mode: Stapler's menu switches it, the choice is remembered ---------- */ + window.__dark = function (on) { + if (on) document.documentElement.setAttribute('data-theme', 'dark'); else document.documentElement.removeAttribute('data-theme'); + var ph = document.querySelector('#phBadge img'); if (ph) ph.src = ph.src.replace(/theme=(light|dark)/, 'theme=' + (on ? 'dark' : 'light')); + try { localStorage.setItem('md-dark', on ? '1' : '0'); } catch (e) { } + var b = $('#darkBtn'), l = $('#darkLbl'); if (b) b.setAttribute('aria-pressed', on ? 'true' : 'false'); if (l) l.textContent = on ? 'Back to light mode' : 'We have a dark mode as well'; + var m = document.querySelector('meta[name="theme-color"]'); if (m) m.setAttribute('content', on ? '#141311' : '#F5F4EF'); + dispatchEvent(new Event('scroll')); + }; + window.__dark(document.documentElement.getAttribute('data-theme') === 'dark'); + + /* ---------- menu: the section links in a dropdown ---------- */ + var menu = $('#menu'), menuBtn = $('#menuBtn'), menuY = 0; + function setMenu(open, back) { + if (menu.classList.contains('open') === open) return; + menu.classList.toggle('open', open); menuBtn.setAttribute('aria-expanded', open ? 'true' : 'false'); + if (open) { + menuY = scrollY; nav.classList.remove('hide'); + $$('#navMenu a[href^="#"]').forEach(function (a) { var on = a.getAttribute('href') === '#' + curSec; a.classList.toggle('cur', on); if (on) a.setAttribute('aria-current', 'location'); else a.removeAttribute('aria-current'); }); + } + if (!open && back) menuBtn.focus(); + } + menuBtn.addEventListener('click', function () { setMenu(!menu.classList.contains('open')); }); + $$('#navMenu a').forEach(function (a) { a.addEventListener('click', function () { setMenu(false); }); }); + document.addEventListener('click', function (e) { if (!menu.contains(e.target)) setMenu(false); }); + document.addEventListener('keydown', function (e) { if (e.key === 'Escape' && menu.classList.contains('open')) setMenu(false, true); }); + menu.addEventListener('focusout', function (e) { if (e.relatedTarget && !menu.contains(e.relatedTarget)) setMenu(false); }); + + /* ---------- the hero ---------- */ + (function hero() { + $$('.hav').forEach(function (h, i) { + var c = O.portrait(rec(h.dataset.cast)); c.style.setProperty('--bd', (-i * .7) + 's'); h.appendChild(c); + var say = el('span', 'say', h.dataset.say); h.appendChild(say); + setTimeout(function () { h.classList.add('in'); }, 250 + i * 200); + h.addEventListener('click', function () { h.classList.add('talk'); setTimeout(function () { h.classList.remove('talk'); }, 2000); }); + }); + (document.fonts && document.fonts.ready ? document.fonts.ready : Promise.resolve()).then(function () { document.body.classList.add('ready'); }); + + var layout = SMALL ? 'tall' : 'wide', names = SMALL ? TALL_TEAM : WIDE_TEAM; + var cv = $('#heroMap'), scene = new O.Scene(cv, { layout: layout, theme: 'office', agents: team(names, layout), time: 11, seed: 3 }); + var frame = $('#heroFrame'); + // side list + var side = $('#sideAgents'), dots = {}; + ['ryan', 'pam', 'kelly', 'dwight', 'kevin', 'angela', 'oscar'].forEach(function (nm) { + var c = CAST[nm], r = el('div', 'arow'); + r.appendChild(tile(rec(nm), c[3])); + r.appendChild(el('div', 'who', '' + c[0] + '' + c[1] + ' · ' + c[2] + '')); + var d = el('i', 'dot' + (nm === 'ryan' || nm === 'oscar' ? ' work' : '')); r.appendChild(d); dots[nm] = d; side.appendChild(r); + }); + // floating agent card + var fa = $('#fAgent'), card = agentCard('pam', 'st-idle'); fa.appendChild(card.root); + + // speech near an agent on the map + function sayOnMap(id, text, ms) { + var f = scene.feet(id); if (!f) return; + var k = scale(cv), b = el('div', 'mapsay', text), fr = frame.getBoundingClientRect(), cr = cv.getBoundingClientRect(); + b.style.left = (cr.left - fr.left + f.x * k) + 'px'; b.style.top = (cr.top - fr.top + f.head * k) + 'px'; + frame.appendChild(b); requestAnimationFrame(function () { b.classList.add('on'); }); + setTimeout(function () { b.classList.remove('on'); setTimeout(function () { b.remove(); }, 300); }, ms || 2000); + } + // click an agent: their card + var mc = null; + frame.addEventListener('click', function (e) { + var r = cv.getBoundingClientRect(), k = scale(cv), id = scene.hit((e.clientX - r.left) / k, (e.clientY - r.top) / k); + if (mc) { mc.remove(); mc = null; } + if (!id || !CAST[id]) return; + var f = scene.feet(id), fr = frame.getBoundingClientRect(); + mc = el('div', 'mapcard card acard'); var ac = agentCard(id, dots[id] && dots[id].classList.contains('work') ? 'st-work' : 'st-think'); mc.appendChild(ac.root); + mc.style.left = clamp(r.left - fr.left + f.x * k, 150, fr.width - 150) + 'px'; mc.style.top = (r.top - fr.top + f.head * k) + 'px'; + frame.appendChild(mc); requestAnimationFrame(function () { mc && mc.classList.add('on'); }); + scene.bubble(id, 'heart', 1.6); + var m = mc; setTimeout(function () { if (m === mc) { m.classList.remove('on'); setTimeout(function () { m.remove(); }, 300); mc = null; } }, 2600); + }); + + // the composer script + var to = $('#compTo'), txt = $('#compTxt'), send = $('#compSend'), bell = $('#bell'), fask = $('#fAsk'), live = false, loopOn = false; + var SCRIPT = [ + { to: 'pam', text: 'make the launch hero pop, light and dark', reply: 'On it', ask: { q: 'Ship the light hero or the dark one first?', o: ['Light', 'Dark'] } }, + { to: 'kelly', text: 'answer the new tickets, drafts only', reply: 'Drafting now' }, + { to: 'pam', text: 'and a version for phones', reply: 'Next up', queued: true }, + { to: 'dwight', text: 'research the accounts for Monday', reply: 'Already on it' }, + { to: 'angela', text: 'match last month\'s payments to invoices', reply: 'Reconciling', ask: { q: 'Two invoices have no payment yet. Flag them?', o: ['Flag them', 'Wait a week'] } } + ]; + function flyEnvelope(fromEl, id) { + return new Promise(function (done) { + var f = scene.feet(id); if (!f) return done(); + var a = fromEl.getBoundingClientRect(), r = cv.getBoundingClientRect(), k = scale(cv); + var x0 = a.left + a.width / 2 - 14, y0 = a.top - 10, x1 = r.left + f.x * k - 14, y1 = r.top + f.head * k - 20; + var env = el('div', 'env', ''); + document.body.appendChild(env); + if (RM) { env.remove(); return done(); } + var t0 = performance.now(), D = 720; + (function step(now) { + var p = clamp((now - t0) / D, 0, 1), e = p < .5 ? 2 * p * p : 1 - Math.pow(-2 * p + 2, 2) / 2; + var x = lerp(x0, x1, e), y = lerp(y0, y1, e) - Math.sin(p * Math.PI) * 120; + env.style.transform = 'translate(' + x + 'px,' + y + 'px) rotate(' + ((1 - p) * -18) + 'deg) scale(' + (1 - p * .35) + ')'; + if (p < 1) requestAnimationFrame(step); else { env.remove(); done(); } + })(t0); + }); + } + async function runScript() { + if (loopOn) return; loopOn = true; + var i = 0; + while (true) { + if (!live) { await wait(400); continue; } + var s = SCRIPT[i % SCRIPT.length], c = CAST[s.to]; + to.innerHTML = ''; to.appendChild(headCanvas(rec(s.to))); to.appendChild(el('b', '', '@' + c[0])); + txt.classList.remove('ph'); txt.textContent = ''; + for (var k = 0; k < s.text.length; k++) { txt.textContent = s.text.slice(0, k + 1); await wait(RM ? 0 : 34); } + await wait(420); + send.classList.remove('go'); void send.offsetWidth; send.classList.add('go'); + var busy = s.queued; + await flyEnvelope(send, s.to); + txt.classList.add('ph'); txt.textContent = 'Message any agent. Enter sends.'; to.innerHTML = ''; + if (busy) { to.appendChild(el('span', 'st st-queue', 'Queued')); } + scene.bubble(s.to, 'mail', 1.2); + if (dots[s.to]) dots[s.to].className = 'dot work'; + if (s.to === 'pam') setSt(card.st, 'st-work'); + setTimeout(function (id, rep) { sayOnMap(id, rep, 1800); scene.bubble(id, 'dots', 4); }, 500, s.to, s.reply); + if (s.ask) { + await wait(2600); + scene.bubble(s.to, 'ask', 4); bell.classList.add('on'); if (dots[s.to]) dots[s.to].className = 'dot asking'; + if (s.to === 'pam') setSt(card.st, 'st-ask'); + fask.hidden = false; fask.innerHTML = ''; + var h = el('div', 'ask-h', '' + c[0] + ' asks you'); + var p = el('p', '', s.ask.q), opts = el('div', 'opts'); + var picked = null, waitPick = new Promise(function (res) { + s.ask.o.forEach(function (o) { var b = el('button', '', o); b.type = 'button'; b.addEventListener('click', function () { picked = b; res(); }); opts.appendChild(b); }); + setTimeout(res, 3400); + }); + fask.appendChild(h); fask.appendChild(p); fask.appendChild(opts); + fask.classList.remove('pop'); void fask.offsetWidth; fask.classList.add('pop'); + await waitPick; (picked || opts.firstChild).classList.add('pick'); + await wait(700); + fask.innerHTML = '
Answer sent to ' + c[0] + '
'; + bell.classList.remove('on'); if (dots[s.to]) dots[s.to].className = 'dot work'; if (s.to === 'pam') setSt(card.st, 'st-work'); + scene.bubble(s.to, 'check', 2); + await wait(1500); fask.hidden = true; + } + await wait(busy ? 1400 : 1900); + to.innerHTML = ''; + i++; + } + } + whenVisible($('#stagewrap'), function () { live = true; if (!RM) scene.start(); else scene.draw(); runScript(); }, function () { live = false; scene.stop(); }); + + // depth: the window tilts flat as you scroll; the cards drift at their own speeds + var app = $('#app'), sw = $('#stagewrap'), mx = 0, my = 0; + if (HOVER && !RM) document.querySelector('.hero').addEventListener('pointermove', function (e) { mx = e.clientX / innerWidth - .5; my = e.clientY / innerHeight - .5; }); + window.__heroTick = function () { + if (RM) return; + var r = sw.getBoundingClientRect(), p = clamp(1 - (r.top - innerHeight * .25) / (innerHeight * .75), 0, 1); + var rx = lerp(14, 0, p) + my * -2.5, ry = mx * 3.5, sc = lerp(.93, 1, p); + app.style.transform = 'rotateX(' + rx.toFixed(2) + 'deg) rotateY(' + ry.toFixed(2) + 'deg) scale(' + sc.toFixed(3) + ')'; + var y = r.top; + fa.style.transform = 'translate(' + (mx * -18).toFixed(1) + 'px,' + ((1 - p) * 60 + my * -12 + y * -.06).toFixed(1) + 'px)'; + fask.style.transform = 'translate(' + (mx * 22).toFixed(1) + 'px,' + ((1 - p) * 90 + my * 14 + y * .04).toFixed(1) + 'px)'; + }; + })(); + + /* ---------- team ---------- */ + (function teamSection() { + var NAMES = ['Nova', 'Remy', 'Juno', 'Kai', 'Ivy', 'Otis', 'Mila', 'Theo', 'Zara', 'Leo', 'Ada', 'Rio', 'Wren', 'Arlo', 'Nell', 'Sol', 'Bea', 'Ezra']; + var ROLES = [ + ['Product designer', 'You own how a screen works and reads. You build three directions as real pages, check each by keyboard and at each size, and hand back one pick with reasons.'], + ['Research analyst', 'You own the facts behind a decision. You read each source yourself and hand back a brief where every claim has a source you opened.'], + ['Executive assistant', "You own one person's inbox, calendar and task list. You sort what came in, draft replies in their voice, and hand back a short briefing before each meeting."], + ['Customer support', 'You own the support queue. You sort each ticket, reproduce the problem in a browser, and hand back a draft reply with the facts checked.'], + ['Growth marketer', 'You own the funnel numbers. You find the biggest leak, write one test with its measure and its stop rule, and hand back the result with the query.'], + ['Bookkeeper', 'You own the books. You sort each transaction, match the bank, card and payment balances, and hand back the month with every gap listed.'] + ]; + var ENGS = ['Claude Code', 'Codex', 'Gemini CLI', 'Grok', 'Cursor', 'Copilot CLI']; + var TINTS = ['#F7E8F0', '#E6EFF6', '#F8F5E5', '#EEECF5', '#E7F0EB', '#F4E9E0', '#FFF3D2']; + var faces = $('#faces'), roles = $('#roles'), engs = $('#engs'), big = $('#pvBig'), seed = 40; + var pool = ['pam', 'jim', 'kelly', 'oscar', 'angela', 'andy', 'phyllis', 'stanley', 'meredith', 'kevin', 'toby', 'creed'].map(function (n) { return { name: CAST[n][0], recipe: rec(n) }; }); + for (var i = 0; i < 3; i++) pool.push({ name: NAMES[i], recipe: A.presetRecipe(i + 2) }); + var state = { face: 0, role: 0, eng: 0, tint: TINTS[0] }; + function drawFaces(roll) { + faces.innerHTML = ''; + var dice = el('button', 'face dice', ''); + dice.type = 'button'; dice.setAttribute('aria-label', 'Shuffle the faces'); dice.addEventListener('click', shuffle); faces.appendChild(dice); + pool.forEach(function (p, i) { + var b = el('button', 'face' + (i === state.face ? ' on' : '') + (roll ? ' roll' : '')); b.type = 'button'; b.setAttribute('role', 'radio'); b.setAttribute('aria-label', p.name); + b.style.animationDelay = (i * 25) + 'ms'; + var c = O.portrait(p.recipe); c.style.animationDelay = (i * 22) + 'ms'; b.appendChild(c); + b.addEventListener('click', function () { state.face = i; state.tint = TINTS[i % TINTS.length]; $$('.face', faces).forEach(function (f) { f.classList.remove('on'); }); b.classList.add('on'); preview(true); }); + faces.appendChild(b); + }); + } + function shuffle() { + pool = pool.map(function (p, i) { seed += 7; return { name: NAMES[(seed + i) % NAMES.length], recipe: A.randomRecipe(seed * 131 + i) }; }); + state.face = 0; drawFaces(true); preview(true); + } + function seg(host, list, key) { + host.innerHTML = ''; + list.forEach(function (l, i) { + var b = el('button', i === state[key] ? 'on' : '', key === 'eng' ? engHTML(l) : Array.isArray(l) ? l[0] : l); b.type = 'button'; b.setAttribute('role', 'radio'); + b.addEventListener('click', function () { state[key] = i; $$('button', host).forEach(function (x) { x.classList.remove('on'); }); b.classList.add('on'); preview(false); }); + host.appendChild(b); + }); + if (key === 'eng') { var more = el('span', 'chip', 'and six more'); more.style.height = '34px'; host.appendChild(more); } + } + function preview(pop) { + var p = pool[state.face]; + big.innerHTML = ''; big.style.background = state.tint; big.appendChild(O.portrait(p.recipe)); + if (pop) { big.classList.remove('pop'); void big.offsetWidth; big.classList.add('pop'); } + $('#pvName').textContent = p.name; $('#pvRole').textContent = ROLES[state.role][0]; $('#pvEng').innerHTML = engHTML(ENGS[state.eng]); + $('#roleText').textContent = ROLES[state.role][1]; + $('#hire').textContent = 'Hire ' + p.name; + } + drawFaces(false); seg(roles, ROLES, 'role'); seg(engs, ENGS, 'eng'); preview(false); + + // the strip: a small office the hires walk into + var cv = $('#stripMap'), scene = new O.Scene(cv, { layout: 'strip', theme: 'office', agents: [{ id: 'ryan', name: 'Ryan', recipe: rec('ryan'), desk: 0 }, { id: 'pam', name: 'Pam', recipe: rec('pam'), desk: 1 }], time: 11, seed: 9 }); + var order = ['ryan', 'pam'], hires = 0, cap = $('#stripCap'), hiring = false; + function capText() { cap.textContent = 'Your office · ' + order.length + (order.length === 1 ? ' agent' : ' agents'); } + capText(); + whenVisible(cv, function () { if (!RM) scene.start(); }, function () { scene.stop(); }); + $('#hire').addEventListener('click', function () { + if (hiring) return; hiring = true; setTimeout(function () { hiring = false; }, RM ? 0 : 700); + scene.agents = scene.agents.filter(function (a) { return !(a.hidden && order.indexOf(a.id) < 0); }); + var p = pool[state.face], id = 'h' + (++hires), used = scene.agents.filter(function (a) { return !a.hidden && a.then !== 'gone'; }).map(function (a) { return a.desk; }); + var desk = -1; for (var d = 0; d < 5; d++) if (used.indexOf(d) < 0) { desk = d; break; } + var go = function (dk) { + scene.add({ id: id, name: p.name, recipe: p.recipe, desk: dk }, { enter: true }); + setTimeout(function () { scene.bubble(id, 'heart', 2); }, 1600); + }; + // the card flies to the door + var from = big.getBoundingClientRect(), r = cv.getBoundingClientRect(), k = scale(cv); + var fly = el('div', 'flyer'); fly.appendChild(O.portrait(p.recipe, 3)); document.body.appendChild(fly); + var tx = r.left + (O.LAYOUTS.strip.door.x * 16 + 16) * k - 27, ty = r.top + 8 * k; + if (RM) { fly.remove(); } + else { + var t0 = performance.now(); + (function step(now) { + var q = clamp((now - t0) / 650, 0, 1), e = 1 - Math.pow(1 - q, 3); + fly.style.transform = 'translate(' + lerp(from.left + 9, tx, e) + 'px,' + (lerp(from.top, ty, e) - Math.sin(q * Math.PI) * 90) + 'px) scale(' + (1 - q * .5) + ')'; + if (q < 1) requestAnimationFrame(step); else { fly.remove(); burst(tx + 27, ty + 20); } + })(t0); + } + if (desk < 0) { var old = order.shift(), oa = scene.get(old); desk = oa ? oa.desk : 0; scene.leave(old); } + order.push(id); capText(); + setTimeout(function () { go(desk); }, RM ? 0 : 600); + // next face, so the next hire is someone new + state.face = (state.face + 1) % pool.length; state.tint = TINTS[state.face % TINTS.length]; + $$('.face', faces).forEach(function (f, i) { f.classList.toggle('on', i - 1 === state.face); }); + preview(true); + }); + })(); + function burst(x, y) { + if (RM) return; + var cols = ['#FFC94F', '#E060A8', '#4F9FD9', '#3F8F5E', '#E0803A', '#9482D3']; + for (var i = 0; i < 18; i++) (function (i) { + var b = el('i', 'burst'); b.style.background = cols[i % cols.length]; document.body.appendChild(b); + var a = Math.random() * Math.PI * 2, v = 60 + Math.random() * 80, t0 = performance.now(); + (function step(now) { + var t = (now - t0) / 700; if (t >= 1) return b.remove(); + b.style.transform = 'translate(' + (x + Math.cos(a) * v * t) + 'px,' + (y + Math.sin(a) * v * t + 120 * t * t) + 'px)'; b.style.opacity = 1 - t; + requestAnimationFrame(step); + })(t0); + })(i); + } + + /* ---------- engines ---------- */ + (function engines() { + var E = ['claude', 'codex', 'gemini', 'grok', 'copilot', 'cursor', 'antigravity', 'kimi', 'qwen', 'opencode', 'crush', 'pi'], NAME = { copilot: 'Copilot CLI' }; + var m = $('#marq'); + function card(k) { + var e = BR.engines[k], c = el('div', 'ecard'); if (!e) return c; + c.style.cssText = '--e-bg:' + e.bg + ';--e-fg:' + e.fg + ';--e-sub:' + e.sub + ';--e-chip:' + e.chip; + c.innerHTML = '
Signed in
' + (NAME[k] || e.name) + '
' + e.plan + '
'; + return c; + } + function row(list, rev, dur) { + var r = el('div', 'mrow' + (rev ? ' rev' : '')); r.style.setProperty('--dur', dur + 's'); + list.concat(list).forEach(function (k) { r.appendChild(card(k)); }); + return r; + } + m.appendChild(row(E.slice(0, 6).concat(E.slice(6, 9)), false, SMALL ? 40 : 56)); + m.appendChild(row(E.slice(6).concat(E.slice(0, 3)), true, SMALL ? 46 : 64)); + })(); + + /* ---------- simulations: every tile is a loop recorded in the app (0.5.8). A tile plays only while on screen; + with reduced motion it keeps its poster. A tile whose poster is missing hides itself, and the Build your own + tile widens to close the row, so the page never shows an empty tile. ---------- */ + (function simulations() { + var grid = $('#sims'); if (!grid) return; + var tiles = $$('.sim[data-sim]', grid); + function fit() { + var n = tiles.filter(function (t) { return !t.hidden; }).length; + grid.style.setProperty('--byo3', 'span ' + (3 - n % 3)); grid.style.setProperty('--byo2', 'span ' + (2 - n % 2)); + } + tiles.forEach(function (t) { + var img = $('img', t), v = $('video', t); + function gone() { t.hidden = true; fit(); } + img.addEventListener('error', gone); + if (img.complete && !img.naturalWidth && img.getAttribute('src')) gone(); + v.muted = true; + v.addEventListener('playing', function () { t.classList.add('live'); }); + v.addEventListener('error', function () { t.classList.remove('live'); }); + if (RM) return; + whenVisible(t, function () { + if (t.hidden) return; + if (!v.getAttribute('src')) v.src = v.dataset.src; + var p = v.play(); if (p && p.catch) p.catch(function () { }); + }, function () { if (!v.paused) v.pause(); }); + }); + fit(); + // Build your own: shows the steps an agent takes in the app, from the one line typed here + var input = $('#tprompt'), steps = $('#tsteps'), PH = ['on a beach', 'in a ski cabin', 'in a submarine lab', 'in a night market', 'on a farm'], phi = 0, building = false; + setInterval(function () { if (document.activeElement !== input && !input.value) { phi = (phi + 1) % PH.length; input.placeholder = PH[phi]; } }, 2200); + async function build() { + if (building) return; building = true; + steps.innerHTML = ''; + var L = ['Reading The Office', 'Drawing the room', 'Drawing the cast', 'Writing their lines', 'Checking it works']; + for (var i = 0; i < L.length; i++) { + var c = el('span', 'chip', '' + L[i]); steps.appendChild(c); + requestAnimationFrame(function (c) { return function () { c.classList.add('in'); }; }(c)); + await wait(RM ? 0 : 420); c.classList.add('done'); c.querySelector('i').style.color = 'var(--ok)'; + } + var note = el('span', 'note', 'In the app, an agent builds it from that one line.'); + steps.appendChild(note); requestAnimationFrame(function () { note.classList.add('in'); }); + building = false; + } + $('#tbuild').addEventListener('click', build); + input.addEventListener('keydown', function (e) { if (e.key === 'Enter') build(); }); + })(); + + /* ---------- yours: the whole office is a folder of plain files on your computer (round 13) ---------- */ + (function local() { + var scr = $('#memScreen'), tree = $('#fwTree'); if (!scr || !tree) return; + var nameEl = $('#fwName'), text = $('#fwText'), saved = $('#fwSaved'), menu = $('#fwMenu'), cap = $('#fwCap'), capT = $('#fwCapT'), lock = $('#fwLock'), drive = $('#fwDrive'), dlabel = $('#fwDriveL'), fold = $('#fwFold'); + var CHEV = ''; + var IC = { + dir: '', + file: '' + }; + // the real shape of an office folder: board and tasks at the top, one folder per agent with its memory, handoff and inbox + var ROWS = [['board', 0, 'file', 'board.md'], ['tasks', 0, 'file', 'tasks.json'], ['agents', 0, 'dir', 'agents', 1], + ['pam', 1, 'agent', 'pam', 0], ['kelly', 1, 'agent', 'kelly', 1], ['kmem', 2, 'file', 'memory.md'], ['khand', 2, 'file', 'handoff.md'], + ['dwight', 1, 'agent', 'dwight', 1], ['dmem', 2, 'file', 'memory.md'], ['inbox', 2, 'dir', 'inbox', 1], ['mail', 3, 'file', 'from-pam.json'], ['shared', 0, 'dir', 'shared', 0]]; + var row = {}; + ROWS.forEach(function (r) { + var e = el('div', 'fw-row' + (r[2] === 'file' ? '' : r[4] ? ' open' : ''), (r[2] === 'file' ? '' : CHEV) + (r[2] === 'agent' ? '' : IC[r[2]]) + '' + r[3] + ''); + if (r[2] === 'agent') e.insertBefore(tile(rec(r[0]), CAST[r[0]][3]), e.children[1]); + e.style.setProperty('--d', r[1]); tree.appendChild(e); row[r[0]] = e; + }); + var K = function (k) { return '"' + k + '"'; }, S = function (v, id) { return '"' + v + '"'; }; + var DOC = { + kmem: ['agents/kelly/memory.md', ['# Kelly, customer support', '- Refunds go through Pam first.', '- Customers like short replies.']], + mail: ['agents/dwight/inbox/from-pam.json', ['{', ' ' + K('from') + ': ' + S('pam') + ',', ' ' + K('to') + ': ' + S('dwight') + ',', ' ' + K('work') + ': ' + S('invoice-bug') + ',', ' ' + K('body') + ': ' + S('Can you take the invoice bug today?'), '}']], + tasks: ['tasks.json', ['[', ' { ' + K('id') + ': ' + S('invoice-bug') + ',', ' ' + K('owner') + ': ' + S('dwight') + ',', ' ' + K('status') + ': ' + S('todo', 'fwSt') + ' }', ']']] + }; + var ADD = '- Dark mode requests come mostly from design teams.'; + var on = false; + function sel(k) { Object.keys(row).forEach(function (x) { row[x].classList.toggle('sel', x === k); }); } + function show(k) { nameEl.textContent = DOC[k][0]; text.innerHTML = DOC[k][1].map(function (l) { return '
' + l + '
'; }).join(''); } + async function hold(ms) { var t = 0; while (t < ms) { await wait(100); if (on) t += 100; } } + async function flash(k) { saved.classList.add('on'); row[k].classList.add('edited'); await hold(900); saved.classList.remove('on'); } + async function say(t) { cap.classList.add('out'); await wait(260); capT.textContent = t; cap.classList.remove('out'); } + async function kelly() { + sel('kmem'); show('kmem'); await hold(500); + var line = el('div', 'add'); text.appendChild(line); + for (var i = 1; i <= ADD.length; i++) { line.innerHTML = ADD.slice(0, i) + ''; await wait(24); } + line.textContent = ADD; await flash('kmem'); + } + async function mail() { + row.mail.style.display = ''; row.mail.classList.remove('new'); void row.mail.offsetWidth; row.mail.classList.add('new'); + await hold(400); sel('mail'); show('mail'); await flash('mail'); + } + async function tasks() { sel('tasks'); show('tasks'); await hold(900); var st = $('#fwSt'); st.textContent = '"doing"'; st.classList.add('chg'); await flash('tasks'); } + async function open() { + var items = $$('span', menu); menu.classList.add('on'); + for (var i = 0; i < items.length; i++) { items.forEach(function (it, j) { it.classList.toggle('hi', j === i); }); await hold(450); } + await hold(300); menu.classList.remove('on'); items.forEach(function (it) { it.classList.remove('hi'); }); + } + async function copy() { + drive.classList.add('on'); dlabel.textContent = 'Backup drive'; await hold(500); + var a = fold.getBoundingClientRect(), b = $('.dv', drive).getBoundingClientRect(), s = scr.getBoundingClientRect(); + var g = el('div', 'fw-ghost', IC.dir); g.style.left = (a.left - s.left) + 'px'; g.style.top = (a.top - s.top) + 'px'; scr.appendChild(g); + var dx = b.left + b.width / 2 - a.left - 9, dy = b.top + b.height / 2 - a.top - 9; + await new Promise(function (res) { g.animate([{ transform: 'none', opacity: 1 }, { transform: 'translate(' + dx * .5 + 'px,' + (dy * .5 - 30) + 'px) scale(1.5)', opacity: 1, offset: .55 }, { transform: 'translate(' + dx + 'px,' + dy + 'px) scale(.7)', opacity: 0 }], { duration: 1100, easing: 'cubic-bezier(.4,0,.2,1)' }).onfinish = res; }); + g.remove(); dlabel.textContent = 'Copied'; drive.classList.add('done'); await hold(1300); drive.classList.remove('on', 'done'); + } + async function guard() { lock.classList.add('glow'); await hold(1800); lock.classList.remove('glow'); } + var STEPS = [ + ['Kelly learned something. It is saved in a file on your computer.', kelly], + ['Pam asked Dwight for help. The message is a file too.', mail], + ['Tasks and the board are plain files as well.', tasks], + ['Open any of it in any app you like. No lock in.', open], + ['Copy the folder to back it up, or to move to a new computer.', copy], + ['Private and secure: your whole office stays on your disk.', guard] + ]; + function reset() { row.mail.style.display = 'none'; Object.keys(row).forEach(function (x) { row[x].classList.remove('edited'); }); } + if (RM) { sel('kmem'); show('kmem'); text.appendChild(el('div', 'add', ADD)); capT.textContent = 'Your whole office is plain files on your computer: private, yours, and easy to move.'; return; } + reset(); sel('kmem'); show('kmem'); capT.textContent = STEPS[0][0]; + async function loop() { + while (true) { + for (var i = 0; i < STEPS.length; i++) { while (!on) await wait(300); await say(STEPS[i][0]); await STEPS[i][1](); await hold(1400); } + reset(); + } + } + var started = false; + whenVisible(scr, function () { on = true; if (!started) { started = true; loop(); } }, function () { on = false; }, '0px'); + })(); + + /* ---------- sandbox: one glass office, your files outside it ---------- */ + (function sandbox() { + var cv = $('#sbxMap'), glass = $('#glass'), nope = $('#nope'); if (!cv) return; + var scene = new O.Scene(cv, { layout: 'box', theme: 'office', agents: team(['kevin', 'creed', 'andy', 'meredith', 'oscar'], 'box'), time: 11, seed: 21 }); + cv.__scene = scene; // read by the page checks in landing-059-src/check.cjs + // your things, drawn as pixel art: filing cabinets, folders and files + var OUT = '#26222E'; + function px(w, h, rects, sc) { return ''; } + function cabinet() { var r = [[0, 0, 14, 20, OUT], [1, 1, 12, 18, '#B8BEC6'], [1, 1, 12, 1, '#DDE2E7']]; for (var i = 0; i < 3; i++) { r.push([2, 2 + i * 6, 10, 5, '#CBD0D7'], [2, 6 + i * 6, 10, 1, '#99A0AA'], [5, 4 + i * 6, 4, 1, OUT], [6, 3 + i * 6, 2, 1, '#FFFFFF']); } return px(14, 20, r, 3); } + function folder(c, hi) { return px(16, 13, [[0, 1, 7, 12, OUT], [0, 3, 16, 10, OUT], [1, 2, 5, 2, c], [1, 4, 14, 8, hi], [1, 4, 14, 1, '#FFF3D2'], [1, 11, 14, 1, c]], 3); } + function doc(c) { return px(12, 15, [[0, 0, 12, 15, OUT], [1, 1, 10, 13, '#FFFFFF'], [8, 1, 3, 3, '#D6DCE3'], [2, 5, 8, 1, '#B8BEC6'], [2, 7, 8, 1, '#B8BEC6'], [2, 9, 5, 1, '#B8BEC6'], [2, 11, 4, 2, c]], 3); } + var L = [['Filing cabinet', cabinet()], ['Documents', folder('#D9A23A', '#F6C453')], ['Photos', folder('#3F8FC0', '#7DB9E6')]]; + var R = [['Finances.xlsx', doc('#3F8F5E')], ['Contracts.pdf', doc('#E0803A')], ['Archive', cabinet()]]; + function fill(host, list) { list.forEach(function (it) { host.appendChild(el('div', 'it', it[1] + '' + it[0] + '')); }); } + fill($('#stuffL'), L); fill($('#stuffR'), R); + var LINES = ['Nope. Glass.', 'Not my folder.', 'This is my box.'], li = 0, lastBump = 0, held = null, auto = true; + var LIM = { x0: 14, x1: cv.width - 14, y0: 44, y1: cv.height - 6 }; + // past the edge an agent stretches a few pixels, then stops: the glass + function band(v, lo, hi) { var D = 5; if (v < lo) return lo - D * (1 - 1 / ((lo - v) / (D * 2) + 1)); if (v > hi) return hi + D * (1 - 1 / ((v - hi) / (D * 2) + 1)); return v; } + function at(e) { var r = cv.getBoundingClientRect(), k = r.width / cv.width; return { x: (e.clientX - r.left) / k, y: (e.clientY - r.top) / k }; } + function bump(side) { + var now = performance.now(); if (now - lastBump < 1100) return; lastBump = now; + nope.textContent = LINES[li++ % LINES.length]; + glass.classList.remove('bump'); void glass.offsetWidth; glass.classList.add('bump'); setTimeout(function () { glass.classList.remove('bump'); }, 1000); + var host = side === 'l' ? $('#stuffL') : side === 'r' ? $('#stuffR') : null; + if (host) { var it = host.children[Math.floor(Math.random() * host.children.length)]; it.classList.remove('shake'); void it.offsetWidth; it.classList.add('shake'); } + } + function moveTo(id, wx, wy) { + scene.hold(id, band(wx, LIM.x0, LIM.x1), band(wy, LIM.y0, LIM.y1)); + if (!scene.running) scene.draw(); + if (wx < LIM.x0 - 10) bump('l'); else if (wx > LIM.x1 + 10) bump('r'); else if (wy < LIM.y0 - 10 || wy > LIM.y1 + 10) bump('t'); + } + cv.addEventListener('pointerdown', function (e) { + var p = at(e), id = scene.hit(p.x, p.y); if (!id) return; + e.preventDefault(); auto = false; + var a = scene.grab(id); if (!a) return; + held = { id: id, ox: a.fx - p.x, oy: a.fy - p.y }; cv.setPointerCapture(e.pointerId); cv.classList.add('held'); + if (!scene.running) scene.start(); + }); + cv.addEventListener('pointermove', function (e) { + var p = at(e); + if (!held) { if (HOVER) cv.classList.toggle('can', !!scene.hit(p.x, p.y)); return; } + moveTo(held.id, p.x + held.ox, p.y + held.oy); + }); + function release() { if (!held) return; scene.drop(held.id); held = null; cv.classList.remove('held'); } + cv.addEventListener('pointerup', release); cv.addEventListener('pointercancel', release); + // once, when it first shows: someone tries to carry Andy out to your files + function demo() { + if (RM || !auto) return; auto = false; + var id = 'andy', a = scene.grab(id); if (!a) return; + var x0 = a.fx, y0 = a.fy, x1 = cv.width + 40, t0 = performance.now(), D = 1300; + (function step(now) { + var q = clamp((now - t0) / D, 0, 1), e = q < .5 ? 2 * q * q : 1 - Math.pow(-2 * q + 2, 2) / 2; + moveTo(id, lerp(x0, x1, e), y0 - 4 - Math.sin(q * Math.PI) * 10); + if (q < 1) requestAnimationFrame(step); else setTimeout(function () { scene.drop(id); }, 650); + })(t0); + } + whenVisible(glass, function () { if (!RM) scene.start(); else scene.draw(); setTimeout(demo, 1400); }, function () { scene.stop(); }, '0px'); + })(); + + /* ---------- plug and play ---------- */ + (function plug() { + var C = [['slack', 'Slack'], ['gmail', 'Gmail'], ['gcal', 'Google Calendar'], ['gdrive', 'Google Drive'], ['github', 'GitHub'], ['notion', 'Notion'], ['linear', 'Linear'], ['atlassian', 'Atlassian'], + ['hubspot', 'HubSpot'], ['stripe', 'Stripe'], ['sentry', 'Sentry'], ['figma', 'Figma'], ['supabase', 'Supabase'], ['brave', 'Brave Search'], ['canva', 'Canva'], ['airtable', 'Airtable']]; + var SAY = { Slack: 'Kelly can answer in your Slack threads now.', Gmail: 'Kelly can read the support inbox and draft replies.', 'Google Calendar': 'Kelly can see when a customer call is booked.', 'Google Drive': 'Kelly can find the policy docs in your Drive.', GitHub: 'Kelly can link a ticket to the issue that fixes it.', Notion: 'Kelly can read the help pages in Notion.', Linear: 'Kelly can file a bug as a Linear issue.', Atlassian: 'Kelly can file the bug as a Jira ticket.', HubSpot: 'Kelly can see the account before replying.', Stripe: 'Kelly can look up a payment before replying.', Sentry: 'Kelly can match a ticket to the error behind it.', Figma: 'Kelly can check the latest design before answering.', Supabase: 'Kelly can look up the customer record.', 'Brave Search': 'Kelly can search the web for an answer.', Canva: 'Kelly can make a quick graphic for a reply.', Airtable: 'Kelly can update the tracker in Airtable.' }; + var carts = $('#carts'), slots = $('#slots'), say = $('#consSay'), N = 8, filled = []; + if (SMALL) C = C.slice(0, 12); + var cons = agentCard('kelly', 'st-work'); $('#consAgent').appendChild(cons.root); + for (var i = 0; i < N; i++) slots.appendChild(el('div', 'slot', 'Empty')); + function count() { $('#consN').textContent = filled.length + ' plugged in'; } + function render() { // no cap on skills: past eight, the last slot counts the rest + var over = filled.length > N; + $$('.slot', slots).forEach(function (s, i) { + if (over && i === N - 1) { s.className = 'slot full more'; s.innerHTML = '+' + (filled.length - N + 1) + 'more'; return; } + var f = filled[i]; s.className = 'slot' + (f ? ' full' : ''); s.innerHTML = f ? '' + appLogo(f.key) + '' + f.name + '' : 'Empty'; + }); + } + count(); + function plugIn(c, btn) { + if (filled.some(function (f) { return f.name === c[1]; })) return; + filled.push({ name: c[1], key: c[0], btn: btn }); btn.classList.add('used'); + var target = slots.children[Math.min(filled.length, N) - 1]; + var from = btn.getBoundingClientRect(), to = target.getBoundingClientRect(); + var fly = btn.cloneNode(true); fly.classList.remove('used'); fly.style.cssText += ';position:fixed;left:' + from.left + 'px;top:' + from.top + 'px;width:' + from.width + 'px;height:' + from.height + 'px;margin:0;z-index:60;pointer-events:none;transition:none'; + document.body.appendChild(fly); + var done = function () { + fly.remove(); + render(); + target.classList.remove('snap'); void target.offsetWidth; target.classList.add('snap'); + say.textContent = c[1] + ' plugged in. ' + (SAY[c[1]] || ''); count(); + }; + if (RM) return done(); + fly.animate([{ transform: 'none' }, { transform: 'translate(' + (to.left - from.left) + 'px,' + (to.top - from.top - 40) + 'px) scale(.75) rotate(-6deg)', offset: .6 }, { transform: 'translate(' + (to.left - from.left) + 'px,' + (to.top - from.top) + 'px) scale(.55)', opacity: .2 }], { duration: 650, easing: 'cubic-bezier(.4,0,.2,1)' }).onfinish = done; + } + C.forEach(function (c) { + var b = el('button', 'cart', '' + appLogo(c[0]) + '' + c[1] + 'Official MCP server'); b.type = 'button'; b.setAttribute('aria-label', 'Plug in ' + c[1]); + b.addEventListener('click', function () { plugIn(c, b); }); + carts.appendChild(b); + }); + var auto = false; + whenVisible(carts, function () { if (auto) return; auto = true; setTimeout(function () { plugIn(C[1], carts.children[1]); }, 700); setTimeout(function () { plugIn(C[0], carts.children[0]); }, 1500); setTimeout(function () { plugIn(C[5], carts.children[5]); }, 2300); setTimeout(function () { plugIn(C[8], carts.children[8]); }, 3100); }); + })(); + + /* ---------- Stapler ---------- */ + var looks = { seed: 'puck', shape: 'capsule', expression: 'idle', color: '#FFC94F' }; // the app's Stapler: seed and colour from shared/puck.ts, capsule as the founder showed (7 Nov) + (function stapler() { + var stage = $('#stage'), ring = $('#ring'), disc = $('#disc'), demo = $('#demo'), size = SMALL ? 104 : 128, open = false, invisible = false; + function paint() { disc.innerHTML = S.face(looks, size, true, 'always'); } + paint(); + function buildRing() { + var cx = stage.clientWidth / 2, cy = stage.clientHeight / 2, r = size * S.RING.radius, btn = Math.round(size * S.RING.button), orbit = r * 2; + var html = '
'; + Object.keys(S.RING_ANGLE).forEach(function (a, i) { + var ang = S.RING_ANGLE[a] * Math.PI / 180, x = Math.round(Math.sin(ang) * r), y = Math.round(-Math.cos(ang) * r), side = S.labelSide(S.RING_ANGLE[a]); + var lx = side === 'right' ? (btn / 2 + 10) + 'px' : side === 'left' ? 'calc(-100% - ' + (btn / 2 + 10) + 'px)' : '-50%'; + var ly = side === 'up' ? -(btn / 2 + 14) : side === 'down' ? (btn / 2 + 14) : 0; + var dis = a === 'computerUse'; + html += '
' + + '' + S.RING_LABEL[a] + (S.RING_SUB[a] ? '' + S.RING_SUB[a] + '' : '') + '
'; + }); + ring.innerHTML = html; + } + buildRing(); addEventListener('resize', buildRing); + function setOpen(v) { open = v; ring.classList.toggle('in', v); } + disc.addEventListener('click', function () { setOpen(!open); }); + var dT = 0, recT = 0; + function show(html, ms) { demo.innerHTML = html; demo.classList.add('on'); stage.classList.add('showing'); clearTimeout(dT); dT = setTimeout(function () { demo.classList.remove('on'); stage.classList.remove('showing'); }, ms || 4200); } + function wave() { var w = '
'; for (var i = 0; i < 26; i++) w += ''; return w + '
'; } + async function act(a, fromRing) { + if (a === 'computerUse') return; + if (!fromRing) { setOpen(true); await wait(RM ? 0 : 420); } + var btn = ring.querySelector('[data-action="' + a + '"]'); + if (btn) { btn.classList.add('hit'); await wait(RM ? 0 : 260); btn.classList.remove('hit'); } + setOpen(false); disc.classList.toggle('rec', a === 'meeting' || a === 'message'); + clearTimeout(recT); recT = setTimeout(function () { disc.classList.remove('rec'); }, a === 'meeting' ? 6200 : 4200); + if (a === 'screenshot') { + var f = $('#flash'); f.classList.remove('go'); void f.offsetWidth; f.classList.add('go'); + show('
' + S.glyph('screenshot', 14) + 'Screenshot to Ryan
Note: The spacing on this card is off.
Sent to your orchestrator
'); + } else if (a === 'meeting') { + show('
' + S.glyph('video', 14) + 'Recording meeting
' + wave() + '
', 6200); + var lines = ['Ryan: Themes ship first.', 'Pam: Gallery by Thursday.', 'Kelly: I will tell the customers.'], tr = $('#tr'); + for (var i = 0; i < lines.length && tr; i++) { await wait(900); tr.appendChild(el('div', '', lines[i])); } + } else if (a === 'message') { + show('
' + S.glyph('mic', 14) + 'Record message
' + wave() + '
"Draft the follow up for today\'s calls."
Shown to you before it goes.
'); + } else if (a === 'invisible') { + invisible = !invisible; disc.style.opacity = invisible ? .22 : 1; + show('
' + S.glyph('eye', 14) + (invisible ? 'Invisible' : 'Visible again') + '
' + (invisible ? 'Out of every screen share and recording. Click it again to come back.' : 'Back in the room.') + '
'); + } + } + ring.addEventListener('click', function (e) { var b = e.target.closest('.puck-btn'); if (b) act(b.dataset.action, true); }); + // the action list beside it + var ACTS = [['screenshot', 'Screenshot', 'Capture a region and send it with a note.'], ['meeting', 'Record meeting', 'The microphone into a folder of transcripts, one per meeting.'], ['message', 'Record message', 'Speak it. It is written down and shown to you before it goes.'], ['invisible', 'Leave invisible', 'Hide Stapler from screen shares and recordings in one click.'], ['computerUse', 'Computer use', 'Coming soon.']]; + var host = $('#actions'); + ACTS.forEach(function (x) { + var b = el('button', 'act' + (x[0] === 'computerUse' ? ' dis' : ''), '' + S.glyph(S.ICON[x[0]], 16) + '' + x[1] + '' + x[2] + ''); + b.type = 'button'; b.addEventListener('click', function () { act(x[0]); }); host.appendChild(b); + }); + // colours: the app's twelve swatches + var sw = $('#swatches'); + S.PUCK_SWATCHES.forEach(function (w) { + var b = el('button'); b.type = 'button'; b.style.background = w.hex; b.title = w.name; b.setAttribute('aria-label', w.name); if (w.hex === looks.color) b.classList.add('on'); + b.addEventListener('click', function () { looks.color = w.hex; $$('button', sw).forEach(function (x) { x.classList.remove('on'); }); b.classList.add('on'); paint(); paintBuddy(); }); + sw.appendChild(b); + }); + var shuf = el('button', 'btn btn-line btn-sm', 'New face'); shuf.style.cssText = 'width:auto;height:34px;border-radius:9px;margin-left:6px'; shuf.type = 'button'; + shuf.addEventListener('click', function () { var w = S.SHUFFLE_WORDS, n = looks.seed; while (n === looks.seed) n = w[Math.floor(Math.random() * w.length)]; looks.seed = n; paint(); paintBuddy(); }); // the app's Shuffle: a new seed, same shape + sw.appendChild(shuf); + })(); + + /* ---------- the companion ---------- */ + var buddy = $('#buddy'), bface = $('#buddyFace'), hidden = false; + try { hidden = sessionStorage.getItem('md-buddy-hidden') === '1'; } catch (e) { } + function paintBuddy() { bface.innerHTML = S.face(looks, SMALL ? 50 : 58, true, 'always'); } // the same face as the Stapler section (founder, 7 Nov) + paintBuddy(); + (function () { + var bx = 0, by = 0, sx = 0, sy = 0, moved = false, down = false, said = false; + buddy.addEventListener('pointerdown', function (e) { if (e.target.closest('.bmenu')) return; e.preventDefault(); down = true; moved = false; sx = e.clientX - bx; sy = e.clientY - by; buddy.setPointerCapture(e.pointerId); }); + buddy.addEventListener('pointermove', function (e) { if (!down) return; var nx = e.clientX - sx, ny = e.clientY - sy; if (Math.abs(nx - bx) + Math.abs(ny - by) > 4) moved = true; if (moved) { bx = nx; by = ny; buddy.style.translate = bx + 'px ' + by + 'px'; } }); + function setOpen(v) { buddy.classList.toggle('open', v); bface.setAttribute('aria-expanded', v ? 'true' : 'false'); } + buddy.addEventListener('pointerup', function (e) { if (!down) return; down = false; if (!moved && !e.target.closest('.bmenu')) setOpen(!buddy.classList.contains('open')); }); + bface.addEventListener('keydown', function (e) { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); setOpen(!buddy.classList.contains('open')); } }); + buddy.addEventListener('keydown', function (e) { if (e.key === 'Escape' && buddy.classList.contains('open')) { setOpen(false); bface.focus(); } }); + $$('#bmenu a').forEach(function (a) { a.addEventListener('click', function () { setOpen(false); }); }); + $('#darkBtn').addEventListener('click', function () { window.__dark(document.documentElement.getAttribute('data-theme') !== 'dark'); setOpen(false); }); + $('#buddyHide').addEventListener('click', function () { hidden = true; try { sessionStorage.setItem('md-buddy-hidden', '1'); } catch (e) { } buddy.classList.remove('on', 'open'); }); + window.__buddy = function (show) { + if (hidden) show = false; + buddy.classList.toggle('on', show); if (!show) buddy.classList.remove('open'); + if (show && !said) { said = true; setTimeout(function () { buddy.classList.add('talk'); setTimeout(function () { buddy.classList.remove('talk'); }, 3600); }, 700); } + }; + })(); + var curSec = 'top'; + var sio = new IntersectionObserver(function (es) { es.forEach(function (e) { if (e.isIntersecting) curSec = e.target.id; }); }, { rootMargin: '-45% 0px -45% 0px' }); + $$('main > section').forEach(function (s) { sio.observe(s); }); + + /* ---------- 24x7 ---------- */ + var always = (function () { + var sec = $('#always'), layout = SMALL ? 'tall' : 'wide'; + var names = (SMALL ? TALL_TEAM : WIDE_TEAM).slice(0, O.LAYOUTS[layout].desks.length - 1); + var ag = team(names, layout); ag.push({ id: 'you', name: 'You', recipe: A.presetRecipe(5), desk: names.length }); + var cv = $('#nightMap'), scene = new O.Scene(cv, { layout: layout, theme: 'office', agents: ag, time: 9, seed: 13 }); + var EV = [ + [9, 'You open your laptop', 'Agents pick up where they left off', 'ryan', 'sun'], + [12.5, 'Webhook: a new support email', 'Kelly drafts a reply for you to check', 'kelly', 'bolt'], + [18, 'Schedule: daily summary', 'Sent to you by email', 'ryan', 'clock'], + [22, 'Laptop closed', 'Your Cloud Office keeps going', 'oscar', 'cloud'], + [26, 'Schedule: nightly research digest', 'Waiting in your Inbox', 'oscar', 'clock'], + [30.5, 'Schedule: morning brief', 'Posted to your Slack channel', 'ryan', 'clock'] + ]; + var IC = { sun: '', bolt: '', clock: '', cloud: '' }; + var list = $('#events'), rows = []; + EV.forEach(function (e) { + var h = e[0] % 24, tm = (h < 10 ? '0' : '') + Math.floor(h) + ':' + (h % 1 ? '30' : '00'); + var r = el('div', 'ev', '' + tm + '' + IC[e[4]] + '' + e[1] + (e[4] === 'cloud' ? ' PRO' : '') + '' + e[2] + ''); + var who = el('span', 'who'); who.appendChild(tile(rec(e[3]), CAST[e[3]][3])); who.firstChild.style.cssText = 'width:24px;height:24px;border-radius:7px'; who.firstChild.firstChild.style.cssText = 'width:20px;height:22px;margin-top:4px'; who.appendChild(document.createTextNode(CAST[e[3]][0])); r.appendChild(who); + list.appendChild(r); rows.push(r); + }); + var clock = $('#clock'), lab = $('#clockLab'), chip = $('#lidchip'), puffs = $('#puffs'), youIn = true, lastH = 9; + for (var i = 0; i < 13; i++) { var p = el('i'); var w = 56 + (i * 37 % 64); p.style.cssText = 'left:' + (i * 8 - 3) + '%;width:' + w + 'px;height:' + w + 'px;bottom:' + (-w * .32) + 'px;transition-delay:' + (i * 40) + 'ms'; puffs.appendChild(p); } + var LAP = ''; + function chipText(cloud) { chip.innerHTML = cloud ? '' + IC.cloud + 'Cloud Office · always on PRO' : LAP + 'Running on your computer'; } + chipText(false); + var running = false; + whenVisible(sec, function () { running = true; if (!RM) scene.start(); else scene.draw(); }, function () { running = false; scene.stop(); }, '0px'); + var BG = [[0, '#F5F4EF', '#1A1916'], [.3, '#F4E7D3', '#1A1916'], [.37, '#EFCB9C', '#1A1916'], [.42, '#3B3046', '#ECEAE6'], [.48, '#131317', '#ECEAE6'], [.86, '#131317', '#ECEAE6'], [.93, '#3B3550', '#ECEAE6'], [.97, '#FFE9B8', '#1A1916'], [1, '#FFF3D2', '#1A1916']]; + // dark mode: the same day and night, in dark tones (round 9) + var BGD = [[0, '#141311', '#F2EFE7'], [.3, '#1D1913', '#F2EFE7'], [.37, '#2A2017', '#F2EFE7'], [.42, '#241E2D', '#ECEAE6'], [.48, '#131317', '#ECEAE6'], [.86, '#131317', '#ECEAE6'], [.93, '#231F33', '#ECEAE6'], [.97, '#2A2417', '#F2EFE7'], [1, '#2A2518', '#F2EFE7']]; + function hexMix(a, b, t) { var x = [1, 3, 5].map(function (i) { return parseInt(a.slice(i, i + 2), 16); }), y = [1, 3, 5].map(function (i) { return parseInt(b.slice(i, i + 2), 16); }); return 'rgb(' + x.map(function (v, i) { return Math.round(v + (y[i] - v) * t); }).join(',') + ')'; } + return function tick() { + var r = sec.getBoundingClientRect(); + if (r.top > innerHeight * 1.5 || r.bottom < -innerHeight * .5) return 0; + var total = sec.offsetHeight - innerHeight, p = clamp(-r.top / total, 0, 1); + var h = 9 + p * 22, hh = h % 24, H = Math.floor(hh), M = Math.floor((hh - H) * 60 / 15) * 15; + clock.firstChild.nodeValue = (H < 10 ? '0' : '') + H + ':' + (M < 10 ? '0' : '') + M; + lab.textContent = hh < 5 ? 'Night' : hh < 12 ? 'Morning' : hh < 17 ? 'Afternoon' : hh < 21 ? 'Evening' : 'Night'; + scene.setTime(hh); + var DM = document.documentElement.getAttribute('data-theme') === 'dark', B = DM ? BGD : BG; + for (var k = 0; k < B.length - 1; k++) if (p >= B[k][0] && p <= B[k + 1][0]) { var t = (p - B[k][0]) / (B[k + 1][0] - B[k][0]); sec.style.setProperty('--bgx', hexMix(B[k][1], B[k + 1][1], t)); sec.style.setProperty('--fgx', t < .5 ? B[k][2] : B[k + 1][2]); break; } + var dark = DM || (p > .395 && p < .95); + sec.style.setProperty('--evb', dark ? '#1B1B21' : '#FFFFFF'); sec.style.setProperty('--evl', dark ? '#34333C' : '#DEDBD2'); sec.style.setProperty('--evk', dark ? '#26262C' : '#ECEAE3'); + var curI = -1; rows.forEach(function (row, i) { var on = h >= EV[i][0]; row.classList.toggle('on', on); if (on) curI = i; }); + rows.forEach(function (row, i) { row.classList.toggle('cur', i === Math.max(0, curI)); }); + var cloud = h >= 22 && h < 30.8; sec.classList.toggle('cloudy', cloud); if (cloud !== chip._c) { chip._c = cloud; chipText(cloud); } + // you leave at 18:30 and come back at 7:00 + var shouldBeIn = h < 18.5 || h >= 30.8; + if (shouldBeIn !== youIn) { youIn = shouldBeIn; if (youIn) scene.come('you'); else scene.leave('you'); } + EV.forEach(function (e) { if (h >= e[0] && lastH < e[0]) { scene.bubble(e[3], e[4] === 'bolt' ? 'mail' : e[4] === 'cloud' ? 'check' : 'idea', 2.4); } }); + lastH = h; + return p; + }; + })(); + + /* ---------- teams ---------- */ + (function teams() { + var net = $('#tNet'), cloud = $('#tCloud'), brain = $('#tBrain'); if (!net) return; + var MOON = ''; + function pill(host, nm, line1, line2, x, y, big) { + var p = el('div', 'office-pill' + (big ? ' big' : '')); p.style.left = x + '%'; p.style.top = y + '%'; + p.appendChild(tile(rec(nm), CAST[nm][3])); + p.appendChild(el('span', '', line1 + (line2 ? '' + line2 + '' : ''))); + host.appendChild(p); return p; + } + function wires(host, pairs) { + var NS = 'http://www.w3.org/2000/svg', svg = document.createElementNS(NS, 'svg'); svg.setAttribute('class', 'tw'); svg.setAttribute('viewBox', '0 0 100 100'); svg.setAttribute('preserveAspectRatio', 'none'); + pairs.forEach(function (q) { var l = document.createElementNS(NS, 'line'); l.setAttribute('x1', q[0]); l.setAttribute('y1', q[1]); l.setAttribute('x2', q[2]); l.setAttribute('y2', q[3]); l.setAttribute('stroke', '#C9C5BA'); l.setAttribute('stroke-width', '1.4'); l.setAttribute('stroke-dasharray', '4 4'); l.setAttribute('vector-effect', 'non-scaling-stroke'); svg.appendChild(l); }); + host.appendChild(svg); + } + // a parcel that travels from one point to another, in percent of the host + function send(host, a, b, label, col, ms) { + return new Promise(function (done) { + var p = el('div', 'parcel', '' + label); host.appendChild(p); + var W = host.clientWidth, H = host.clientHeight, x0 = a[0] / 100 * W, y0 = a[1] / 100 * H, x1 = b[0] / 100 * W, y1 = b[1] / 100 * H, D = ms || 1500; + if (RM) { p.remove(); return done(); } + p.animate([{ transform: 'translate(' + x0 + 'px,' + y0 + 'px) translate(-50%,-50%) scale(.6)', opacity: 0 }, { opacity: 1, offset: .15 }, { opacity: 1, offset: .85 }, { transform: 'translate(' + x1 + 'px,' + y1 + 'px) translate(-50%,-50%) scale(.6)', opacity: 0 }], { duration: D, easing: 'cubic-bezier(.45,0,.3,1)' }).onfinish = function () { p.remove(); done(); }; + }); + } + // 1. Team Network: three people's offices, their agents passing work between them + var N = [[22, 26], [78, 26], [50, 78]]; + wires(net, [[22, 26, 78, 26], [22, 26, 50, 78], [78, 26, 50, 78]]); + pill(net, 'pam', "Pam's office", '3 agents', N[0][0], N[0][1]); pill(net, 'jim', "Jim's office", '4 agents', N[1][0], N[1][1]); pill(net, 'ryan', 'Your office', '5 agents', N[2][0], N[2][1]); + var NET = [[0, 1, 'brief.md', '#FFC94F'], [1, 2, 'Take the pricing page?', '#4F9FD9'], [2, 0, 'Done: pricing page', '#3F8F5E'], [2, 1, 'customer-notes', '#E060A8'], [1, 0, 'Need the logo files', '#9482D3'], [0, 2, 'logos.zip', '#E0803A']]; + // 2. Team Cloud: one central agent in the cloud, everyone connected; each person also has their own Cloud Office + cloud.insertAdjacentHTML('beforeend', ''); + wires(cloud, [[50, 34, 18, 76], [50, 34, 50, 76], [50, 34, 82, 76]]); + pill(cloud, 'oscar', 'Company agent', 'Cloud · shared context', 50, 30, true); + var team3 = [['pam', 18], ['jim', 50], ['kelly', 82]]; + team3.forEach(function (t) { var p = pill(cloud, t[0], CAST[t[0]][0], '', t[1], 76); p.insertAdjacentHTML('beforeend', '' + MOON + ''); }); + var ASK = ['What did we promise Acme?', 'Where is the brand kit?', 'Latest pricing?'], ANS = ['From the March call notes', 'Linked in Drive', 'From the pricing doc']; + // 3. Team Brain: a graph you curate; a role decides what is visible + var KN = [['Sales', 22, 22, '#E0803A'], ['Product', 50, 14, '#4F9FD9'], ['Customers', 78, 22, '#E060A8'], ['Finance', 24, 52, '#3F8F5E'], ['How we work', 52, 44, '#FFC94F'], ['Legal', 78, 52, '#9482D3']]; + var KL = [[0, 1], [1, 2], [0, 4], [1, 4], [2, 4], [3, 4], [4, 5], [0, 3], [2, 5]]; + wires(brain, KL.map(function (l) { return [KN[l[0]][1], KN[l[0]][2], KN[l[1]][1], KN[l[1]][2]]; })); + var LOCK = ''; + var kn = KN.map(function (k) { var n = el('div', 'knode', '' + k[0] + LOCK); n.style.left = k[1] + '%'; n.style.top = k[2] + '%'; brain.appendChild(n); return n; }); + var ROLES = [['Everyone', [0, 1, 2, 4]], ['Sales', [0, 2, 4]], ['Finance', [3, 4, 5]], ['Leads', [0, 1, 2, 3, 4, 5]]]; + var rr = el('div', 'roles'); ROLES.forEach(function (r) { rr.appendChild(el('span', '', r[0])); }); brain.appendChild(rr); + function role(i) { $$('span', rr).forEach(function (s2, j) { s2.classList.toggle('on', j === i); }); kn.forEach(function (n, j) { n.classList.toggle('locked', ROLES[i][1].indexOf(j) < 0); }); } + role(0); + var on = false, started = false; + async function loop() { + var i = 0; + while (true) { + if (!on) { await wait(500); continue; } + var m = NET[i % NET.length]; + send(net, N[m[0]], N[m[1]], m[2], m[3], 1700); + var who = team3[i % 3], q = i % 3; + send(cloud, [who[1], 70], [50, 36], ASK[q], '#4F9FD9', 1300).then(function (w, a) { return function () { return send(cloud, [50, 36], [w[1], 70], a, '#3F8F5E', 1300); }; }(who, ANS[q])); + role((i + 1) % ROLES.length); kn.forEach(function (n) { n.classList.remove('lit'); }); var vis = ROLES[(i + 1) % ROLES.length][1]; kn[vis[i % vis.length]].classList.add('lit'); + await wait(RM ? 3000 : 2600); i++; + } + } + whenVisible($('#teams'), function () { on = true; if (!started) { started = true; loop(); } }, function () { on = false; }, '0px'); + })(); + + /* ---------- film ---------- */ + (function film() { + if ($('#film').hidden) return; + var tv = $('#tv'), v = $('#vid'), play = $('#play'), loaded = false; + function load() { if (loaded) return; loaded = true; v.src = 'media/office-real.mp4'; } + function go() { load(); var pr = v.play(); if (pr && pr.then) pr.then(function () { tv.classList.add('playing'); }, function () { }); } + play.addEventListener('click', go); + v.addEventListener('click', function () { if (v.paused) go(); else { v.pause(); tv.classList.remove('playing'); } }); + whenVisible(tv, function () { if (!RM) go(); }, function () { if (!v.paused) { v.pause(); tv.classList.remove('playing'); } }, '0px'); + })(); + + /* ---------- get: the lineup ---------- */ + (function lineup() { + var host = $('#lineup'), ps = []; + ['angela', 'oscar', 'kevin', 'meredith', 'creed', 'phyllis', 'pam', 'ryan', 'jim', 'dwight', 'kelly', 'andy', 'stanley', 'toby'].forEach(function (nm, i) { + var p = el('div', 'p'), c = O.portrait(rec(nm)); p.appendChild(c); p.appendChild(el('span', 'nm', CAST[nm][0])); + p.addEventListener('click', function () { p.classList.remove('hop'); void p.offsetWidth; p.classList.add('hop'); }); + host.appendChild(p); ps.push(p); + }); + function wave() { if (RM) return; ps.forEach(function (p, i) { setTimeout(function () { p.classList.remove('hop'); void p.offsetWidth; p.classList.add('hop'); }, i * 70); }); } + whenVisible(host, wave, null, '0px'); + $$('#get .btn').forEach(function (b) { b.addEventListener('pointerenter', wave); b.addEventListener('pointerdown', function (e) { burst(e.clientX, e.clientY); }); }); + })(); + + $$('main > section:not([hidden]) .kicker b').forEach(function (b, i) { b.textContent = (i < 9 ? '0' : '') + (i + 1); }); + + /* ---------- one scroll loop ---------- */ + var ticking = false, sendbar = $('#sendbar'), heroCta = $('.hero .cta-row'), getSec = $('#get'), stageW = $('#stagewrap'); + function onScroll() { + ticking = false; + var y = scrollY, H = document.documentElement.scrollHeight - innerHeight, p = H > 0 ? y / H : 0; + nav.classList.toggle('solid', y > 8); + if (menu.classList.contains('open') && Math.abs(y - menuY) > 60) setMenu(false); + nav.classList.toggle('hide', y > 640 && y > lastY + 2 && !nav.matches(':hover') && !menu.classList.contains('open')); + if (y < lastY - 2) nav.classList.remove('hide'); + fill.style.width = (p * 100) + '%'; + walker.style.left = 'calc(' + (p * 100) + '% + ' + (5 - p * 10).toFixed(1) + 'px)'; + walked += Math.abs(y - lastY); drawWalker([0, 1, 0, 2][Math.floor(walked / 30) % 4]); + lastY = y; + if (window.__heroTick) window.__heroTick(); + if (always) always(); + var heroGone = heroCta.getBoundingClientRect().bottom < 0, nearEnd = getSec.getBoundingClientRect().top < innerHeight * .7; + if (OS === 'phone' || SMALL) sendbar.classList.toggle('on', OS === 'phone' && heroGone && !nearEnd); + var stapVis = (function () { var r = $('#stapler').getBoundingClientRect(); return r.top < innerHeight * .8 && r.bottom > innerHeight * .2; })(); + var appGone = stageW.getBoundingClientRect().bottom < innerHeight * .45; + // Off the FAQ cards too (god, 9 Nov): its bubble and face sat on the questions' titles and buttons. + var faqVis = (function () { var f = $('#faq'); if (!f) return false; var r = f.getBoundingClientRect(); return r.top < innerHeight * .8 && r.bottom > innerHeight * .2; })(); + window.__buddy(appGone && !stapVis && !faqVis && !(SMALL && nearEnd)); + } + addEventListener('scroll', function () { if (!ticking) { ticking = true; requestAnimationFrame(onScroll); } }, { passive: true }); + addEventListener('resize', onScroll); + onScroll(); +})(); diff --git a/docs/assets/stapler.css b/docs/assets/stapler.css new file mode 100644 index 0000000000..0669be7d64 --- /dev/null +++ b/docs/assets/stapler.css @@ -0,0 +1,20 @@ +/* Stapler's motion and ring, verbatim from the live harnessmd.com page (blobatar/motion.css, MIT, and the app's puck.css geometry). */ +/* blobatar/motion.css, verbatim: nothing breathes or blinks without it */ +@property --mo-amp{syntax:"";inherits:true;initial-value:0}@property --mo-esx{syntax:"";inherits:true;initial-value:1}@property --mo-esy{syntax:"";inherits:true;initial-value:1}@property --mo-tilt{syntax:"";inherits:true;initial-value:0}@property --mo-edy{syntax:"";inherits:true;initial-value:0}@property --mo-edx{syntax:"";inherits:true;initial-value:0}@property --mo-esx2{syntax:"";inherits:true;initial-value:0}@property --mo-esy2{syntax:"";inherits:true;initial-value:0}@property --mo-tilt2{syntax:"";inherits:true;initial-value:0}@property --mo-edy2{syntax:"";inherits:true;initial-value:0}@property --mo-lock{syntax:"";inherits:true;initial-value:0}@property --mo-shake{syntax:"";inherits:true;initial-value:0}@property --mo-rock{syntax:"";inherits:true;initial-value:0}@property --mo-rockp{syntax:"";inherits:true;initial-value:1}@property --mo-bdy{syntax:"";inherits:true;initial-value:0}.mo-root,.mo-breathe,.mo-bob{transform-box:view-box;transform-origin:center}.mo-root{--mo-amp:0;--mo-morph:calc(.4s*var(--mo-rate,1));--mo-morph-ease:ease-in-out;--mo-tp:--mo-esx,--mo-esy,--mo-tilt,--mo-edy,--mo-edx,--mo-esx2,--mo-esy2,--mo-tilt2,--mo-edy2,--mo-lock,--mo-shake,--mo-rock,--mo-bdy;--mo-md:var(--mo-morph),var(--mo-morph),var(--mo-morph),var(--mo-morph),var(--mo-morph),var(--mo-morph),var(--mo-morph),var(--mo-morph),var(--mo-morph),var(--mo-morph),var(--mo-morph),var(--mo-morph),var(--mo-morph);--mo-me:var(--mo-morph-ease),var(--mo-morph-ease),var(--mo-morph-ease),var(--mo-morph-ease),var(--mo-morph-ease),var(--mo-morph-ease),var(--mo-morph-ease),var(--mo-morph-ease),var(--mo-morph-ease),var(--mo-morph-ease),var(--mo-morph-ease),var(--mo-morph-ease),var(--mo-morph-ease);transition-property:--mo-amp,transform,var(--mo-tp);transition-duration:calc(.4s*var(--mo-rate,1)),calc(.16s*var(--mo-rate,1)),var(--mo-md);transition-timing-function:ease-out,cubic-bezier(.23,1,.32,1),var(--mo-me);animation:mo-shake calc(.112s*var(--mo-rate,1))linear infinite}.mo-bob>g:not(.mo-eyes){fill:var(--mo-head);transition:fill var(--mo-morph)var(--mo-morph-ease)}.mo-eyes{fill:var(--mo-eye);transition:fill var(--mo-morph)var(--mo-morph-ease)}@keyframes mo-shake{0%,to{translate:calc(.62px*var(--mo-shake))calc(-.34px*var(--mo-shake))}25%{translate:calc(-.7px*var(--mo-shake))calc(.22px*var(--mo-shake))}50%{translate:calc(.38px*var(--mo-shake))calc(.66px*var(--mo-shake))}75%{translate:calc(-.44px*var(--mo-shake))calc(-.6px*var(--mo-shake))}}.mo-root.mo-expr{--mo-morph:calc(.3s*var(--mo-rate,1));--mo-morph-ease:cubic-bezier(.45,.05,.5,1)}.mo-root:hover{--mo-amp:1;transition-duration:calc(.4s*var(--mo-rate,1)),calc(.22s*var(--mo-rate,1)),var(--mo-md);transform:translateY(-1.5px)scale(1.04)}.mo-root.mo-always{--mo-amp:1}.mo-breathe{animation-name:mo-breathe;animation-duration:calc(2.8s*var(--mo-rate,1));animation-delay:calc(var(--mo-phase,0s)*var(--mo-rate,1));animation-iteration-count:infinite;animation-direction:alternate;animation-timing-function:ease-in-out}@keyframes mo-breathe{to{transform:scaleX(calc(1 + .022*var(--mo-amp)))scaleY(calc(1 - .018*var(--mo-amp)))}}.mo-bob{translate:0 calc(var(--mo-bdy)*1px);animation-name:mo-bob;animation-duration:calc(3.4s*var(--mo-rate,1));animation-delay:calc(var(--mo-bob-phase,0s)*var(--mo-rate,1));animation-iteration-count:infinite;animation-direction:alternate;animation-timing-function:ease-in-out}@keyframes mo-bob{0%{transform:translateY(0)}to{transform:translateY(calc(-1.1px*var(--mo-amp)))}}.mo-eye{transform-box:view-box;--mo-sel:calc((var(--mo-wrap,1) + 1)/2);--mo-x:calc(var(--mo-esx) + var(--mo-esx2)*var(--mo-sel));--mo-y:calc(var(--mo-esy) + var(--mo-esy2)*var(--mo-sel));--mo-t:calc(var(--mo-tilt) + var(--mo-tilt2)*var(--mo-sel));--mo-ph:calc(var(--mo-sel)*(1 - var(--mo-rock)) + var(--mo-rock)*((1 + var(--mo-wrap,1)*var(--mo-rockp))/2));translate:calc(var(--mo-edx)*var(--mo-wrap,1)*1px)calc((var(--mo-edy) + var(--mo-edy2)*var(--mo-ph))*1px);rotate:calc((var(--mo-t)*var(--mo-wrap,1) - var(--mo-lean,0)*var(--mo-lock))*1deg);transform:rotate(calc(var(--mo-lean,0)*1deg))scaleX(var(--mo-x))scaleY(var(--mo-y))rotate(calc(var(--mo-lean,0)*-1deg));animation-name:mo-rock;animation-duration:calc(.9s*var(--mo-rate,1));animation-iteration-count:infinite;animation-timing-function:ease-in-out}@container style(--mo-rock:0){.mo-eye{animation-name:none}}@keyframes mo-rock{0%,to{--mo-rockp:1}50%{--mo-rockp:-1}}.mo-eye>*{transform-box:fill-box;transform-origin:center;animation-name:mo-blink,mo-wrap;animation-duration:calc(var(--mo-blink,4.8s)*var(--mo-rate,1)),calc(var(--mo-saccade,5.6s)*var(--mo-rate,1));animation-delay:calc(var(--mo-blink-phase,0s)*var(--mo-rate,1)),calc(var(--mo-saccade-phase,0s)*var(--mo-rate,1));animation-iteration-count:infinite;animation-timing-function:linear}@keyframes mo-blink{0%,97.2%{transform:rotate(calc(var(--mo-lean,0)*1deg))scaleY(1)rotate(calc(var(--mo-lean,0)*-1deg));animation-timing-function:ease-in}98.6%{transform:rotate(calc(var(--mo-lean,0)*1deg))scaleY(calc(1 - .92*var(--mo-amp)))rotate(calc(var(--mo-lean,0)*-1deg));animation-timing-function:ease-out}to{transform:rotate(calc(var(--mo-lean,0)*1deg))scaleY(1)rotate(calc(var(--mo-lean,0)*-1deg))}}.mo-eyes{animation-name:mo-saccade;animation-duration:calc(var(--mo-saccade,5.6s)*var(--mo-rate,1));animation-delay:calc(var(--mo-saccade-phase,0s)*var(--mo-rate,1));animation-iteration-count:infinite;animation-timing-function:linear}@keyframes mo-saccade{0%,15%{translate:0}16.5%,31%{translate:calc(-.8px*var(--mo-look-x,1.4)*var(--mo-amp))calc(-.9px*var(--mo-look-y,1.1)*var(--mo-amp))}32.5%,47%{translate:calc(1px*var(--mo-look-x,1.4)*var(--mo-amp))calc(.1px*var(--mo-look-y,1.1)*var(--mo-amp))}48.5%,63%{translate:calc(-.15px*var(--mo-look-x,1.4)*var(--mo-amp))calc(.85px*var(--mo-look-y,1.1)*var(--mo-amp))}64.5%,79%{translate:calc(.75px*var(--mo-look-x,1.4)*var(--mo-amp))calc(-.8px*var(--mo-look-y,1.1)*var(--mo-amp))}80.5%,98.5%{translate:calc(-1px*var(--mo-look-x,1.4)*var(--mo-amp))calc(-.15px*var(--mo-look-y,1.1)*var(--mo-amp))}to{translate:0}}@keyframes mo-wrap{0%,15%{scale:1;rotate:none}16.5%,31%{scale:calc(1 - .0176*var(--mo-look-mx,1.4)*var(--mo-amp) + .008*var(--mo-look-x,1.4)*var(--mo-wrap,1)*var(--mo-amp))calc(1 - .027*var(--mo-look-my,1.1)*var(--mo-amp));rotate:calc(.648deg*var(--mo-look-x,1.4)*var(--mo-look-y,1.1)*var(--mo-wrap,1)*var(--mo-amp))}32.5%,47%{scale:calc(1 - .022*var(--mo-look-mx,1.4)*var(--mo-amp) - .01*var(--mo-look-x,1.4)*var(--mo-wrap,1)*var(--mo-amp))calc(1 - .003*var(--mo-look-my,1.1)*var(--mo-amp));rotate:calc(.09deg*var(--mo-look-x,1.4)*var(--mo-look-y,1.1)*var(--mo-wrap,1)*var(--mo-amp))}48.5%,63%{scale:calc(1 - .0033*var(--mo-look-mx,1.4)*var(--mo-amp) + .0015*var(--mo-look-x,1.4)*var(--mo-wrap,1)*var(--mo-amp))calc(1 - .0255*var(--mo-look-my,1.1)*var(--mo-amp));rotate:calc(-.115deg*var(--mo-look-x,1.4)*var(--mo-look-y,1.1)*var(--mo-wrap,1)*var(--mo-amp))}64.5%,79%{scale:calc(1 - .0165*var(--mo-look-mx,1.4)*var(--mo-amp) - .0075*var(--mo-look-x,1.4)*var(--mo-wrap,1)*var(--mo-amp))calc(1 - .024*var(--mo-look-my,1.1)*var(--mo-amp));rotate:calc(-.54deg*var(--mo-look-x,1.4)*var(--mo-look-y,1.1)*var(--mo-wrap,1)*var(--mo-amp))}80.5%,98.5%{scale:calc(1 - .022*var(--mo-look-mx,1.4)*var(--mo-amp) + .01*var(--mo-look-x,1.4)*var(--mo-wrap,1)*var(--mo-amp))calc(1 - .0045*var(--mo-look-my,1.1)*var(--mo-amp));rotate:calc(.135deg*var(--mo-look-x,1.4)*var(--mo-look-y,1.1)*var(--mo-wrap,1)*var(--mo-amp))}to{scale:1;rotate:none}}@media not ((hover:hover) and (pointer:fine)){.mo-root:hover{--mo-amp:0;transform:none}.mo-root.mo-always{--mo-amp:1}.mo-root:not(.mo-always),.mo-root:not(.mo-always) *{animation-play-state:paused}.mo-root.mo-expr:not(.mo-always) .mo-eye{animation-play-state:running}}.mo-slow{--mo-rate:5}@media (prefers-reduced-motion:reduce){.mo-root,.mo-breathe,.mo-bob,.mo-eyes,.mo-eye,.mo-eye>*{animation:none;transition:none}.mo-bob>g:not(.mo-eyes){transition:none}} +.puck-disc{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);border:0;background:transparent;padding:0;cursor:grab;line-height:0;z-index:2;transition:opacity 160ms ease,filter 160ms ease} +.puck-disc:active{cursor:grabbing} +.puck-ring{position:absolute;inset:0} +.puck-orbit{position:absolute;transform:translate(-50%,-50%) scale(.3);border-radius:50%;border:1px solid rgba(26,19,32,.3);opacity:0;transition:transform 180ms cubic-bezier(.2,.9,.25,1.15),opacity 140ms ease} +.puck-orbit.dashed{border-style:dashed;border-color:rgba(26,19,32,.45);animation:puck-turn 24s linear infinite} +.puck-ring.in .puck-orbit{transform:translate(-50%,-50%) scale(1);opacity:1} +@keyframes puck-turn{to{transform:translate(-50%,-50%) scale(1) rotate(360deg)}} +.puck-btn{position:absolute;display:grid;place-items:center;border-radius:50%;border:1px solid rgba(26,19,32,.14);background:#FFFFFF;color:#1A1320;box-shadow:0 6px 16px rgba(26,19,32,.22),inset 0 1px 0 rgba(255,255,255,.7);transform:translate(-50%,-50%) scale(.2);opacity:0;transition:transform 180ms cubic-bezier(.2,.9,.25,1.2) calc(var(--i) * 18ms),opacity 120ms ease calc(var(--i) * 18ms),background 120ms ease,box-shadow 120ms ease;cursor:pointer;font:inherit} +.puck-ring.in .puck-btn{transform:translate(calc(-50% + var(--x)),calc(-50% + var(--y))) scale(1);opacity:1} +.puck-btn:hover:not(:disabled){background:#ffffff;box-shadow:0 8px 20px rgba(26,19,32,.28),0 0 0 4px rgba(79,159,175,.18)} +.puck-btn:focus-visible{outline:2px solid #4F9FAF;outline-offset:2px} +.puck-btn:disabled{cursor:not-allowed;color:rgba(26,19,32,.38);background:#ECE7DA;box-shadow:0 3px 8px rgba(26,19,32,.12)} +.puck-btn.close{background:rgba(26,19,32,.82);color:#FFF8E7;border-color:transparent} +.puck-label{position:absolute;transform:translate(var(--lx),-50%);font-size:12px;font-weight:600;letter-spacing:.2px;color:#1A1320;text-shadow:0 1px 0 rgba(255,248,231,.95),0 0 8px rgba(255,248,231,.95);white-space:nowrap;opacity:0;transition:opacity 100ms ease;pointer-events:none} +.puck-label small{display:block;font-weight:500;font-size:10px;opacity:.85} +.puck-ring.in .puck-btn:hover+.puck-label,.puck-ring.in .puck-btn:focus-visible+.puck-label{opacity:1} +@media (prefers-reduced-motion:reduce){.puck-orbit,.puck-btn,.puck-label{transition:none;animation:none}} \ No newline at end of file diff --git a/docs/assets/stapler.js b/docs/assets/stapler.js new file mode 100644 index 0000000000..2877228c42 --- /dev/null +++ b/docs/assets/stapler.js @@ -0,0 +1,122 @@ +/* +blobatar 2.7.0 (blobatar.dev), inlined verbatim from the app's own dependency. +MIT License + +Copyright (c) 2026 Alain + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +/* Stapler's face: the blobatar library and the PuckFace port, as the live harnessmd.com page carries them (docs/index.html). */ +window.MDStapler = (function () { + +/* The blobatar library, verbatim from the package the app ships (node_modules/blobatar 2.7.0, MIT). Two scopes: the drawing core and the fourteen poses. */ +var BLOB = (function () { +var G=(t,e)=>`mo-root${t==="always"?" mo-always":""}${e?" mo-expr":""}`;function It(t){let e=Math.round(t.num("motion.blink",3500,6500)),n=Math.round(t.num("motion.saccade",4200,7600)),a=t.num("motion.lookX",1,2.2),o=t.num("motion.lookY",0.8,1.7),r=(s)=>Math.round(s*100)/100;return{phase:Math.round(t.num("motion.phase",0,2800)),bob:Math.round(t.num("motion.bob",0,3400)),blink:e,blinkPhase:Math.round(t.num("motion.blinkPhase",0,e)),saccade:n,saccadePhase:Math.round(t.num("motion.saccadePhase",0,n)),lookX:r(a)*(t.bool("motion.lookXFlip")?-1:1),lookY:r(o)*(t.bool("motion.lookYFlip")?-1:1),lookMX:r(a),lookMY:r(o)}}function N(t){let e=(a)=>`${-a}ms`,n=It(t);return{"--mo-phase":e(n.phase),"--mo-bob-phase":e(n.bob),"--mo-blink":`${n.blink}ms`,"--mo-blink-phase":e(n.blinkPhase),"--mo-look-x":String(n.lookX),"--mo-look-mx":String(n.lookMX),"--mo-look-y":String(n.lookY),"--mo-look-my":String(n.lookMY),"--mo-saccade":`${n.saccade}ms`,"--mo-saccade-phase":e(n.saccadePhase)}}var Ut=(t)=>Object.entries(t).map(([e,n])=>`${e}:${n}`).join(";");function R({l:t,c:e,h:n}){let a=n*Math.PI/180,o=e*Math.cos(a),r=e*Math.sin(a),s=t+0.3963377774*o+0.2158037573*r,c=t-0.1055613458*o-0.0638541728*r,i=t-0.0894841775*o-1.291485548*r,u=s*s*s,m=c*c*c,l=i*i*i;return[4.0767416621*u-3.3077115913*m+0.2309699292*l,-1.2684380046*u+2.6097574011*m-0.3413193965*l,-0.0041960863*u-0.7034186147*m+1.707614701*l]}var U=(t)=>t.every((e)=>e>=-0.0001&&e<=1.0001);function Q(t){let e=R(t);if(!U(e)){let n=0,a=t.c;for(let o=0;o<12;o++){let r=(n+a)/2;if(U(R({...t,c:r})))n=r;else a=r}e=R({...t,c:n})}return e.map((n)=>Math.min(1,Math.max(0,n)))}function q(t){let[e,n,a]=Q(t);return 0.2126*e+0.7152*n+0.0722*a}function B(t,e){let n=q(t),a=q(e);return(Math.max(n,a)+0.05)/(Math.min(n,a)+0.05)}function v(t,e,n){if(B(t,e)>=n)return t;let a=t.l>=e.l?1:-1;for(let s of[a,-a]){let c={...t};for(let i=0;i<60;i++){if(c.l=Math.min(1,Math.max(0,c.l+s*0.02)),B(c,e)>=n)return c;if(c.l===0||c.l===1)break}}let o={...t,l:0,c:0},r={...t,l:1,c:0};return B(o,e)>=B(r,e)?o:r}function T(t){return"#"+Q(t).map((e)=>{let n=e<=0.0031308?12.92*e:1.055*Math.pow(e,0.4166666666666667)-0.055;return Math.round(n*255).toString(16).padStart(2,"0")}).join("")}function S(t){let e=parseInt(t.slice(1),16),[n,a,o]=[e>>16&255,e>>8&255,e&255].map((m)=>{let l=m/255;return l<=0.04045?l/12.92:Math.pow((l+0.055)/1.055,2.4)}),r=Math.cbrt(0.4122214708*n+0.5363325363*a+0.0514459929*o),s=Math.cbrt(0.2119034982*n+0.6806995451*a+0.1073969566*o),c=Math.cbrt(0.0883024619*n+0.2817188376*a+0.6299787005*o),i=1.9779984951*r-2.428592205*s+0.4505937099*c,u=0.0259040371*r+0.7827717662*s-0.808675766*c;return{l:0.2104542553*r+0.793617785*s-0.0040720468*c,c:Math.hypot(i,u),h:Math.atan2(u,i)*180/Math.PI}}function Et(t,e,n){let a=(m)=>m*Math.PI/180,o=t.c*Math.cos(a(t.h)),r=t.c*Math.sin(a(t.h)),s=e.c*Math.cos(a(e.h)),c=e.c*Math.sin(a(e.h)),i=o+(s-o)*n,u=r+(c-r)*n;return{l:t.l+(e.l-t.l)*n,c:Math.hypot(i,u),h:Math.atan2(u,i)*180/Math.PI}}var D=(t,e,n)=>T(Et(S(t),S(e),n));function Dt(t,e,n){let a="#";for(let o=1;o<7;o+=2){let r=parseInt(t.slice(o,o+2),16),s=parseInt(e.slice(o,o+2),16),c=Math.round(r+(s-r)*n);a+=(c<16?"0":"")+c.toString(16)}return a}var Zt={h:27,l:0.58,pull:0.6,c:0.18},Kt={h:358,l:0.72,pull:0.55,c:0.16},Qt={h:12,l:0.84,pull:0.4,c:0.1},Jt={h:142,l:0.66,pull:0.6,c:0.13};var Z=4.55;function Wt(t,e,n){let a=S(t),o=S(e),r={l:a.l+(n.l-a.l)*n.pull,c:Math.max(a.c,n.c),h:n.h};r=v(r,J,W);let s=v(o,r,Z),c=s.l>=r.l?1:-1,i=T(r);for(let u=0;u<40;u++){let m=T(s),l=1/0;for(let p=0;p<=10;p++){let d=p/10;l=Math.min(l,B(S(D(e,m,d)),S(D(t,i,d))))}if(l>=Z)return[i,m];let b=Math.min(1,Math.max(0,s.l+c*0.02));if(b===s.l)return[i,m];s={...s,l:b}}return[i,T(s)]}var K=[[0.2,{l:0.86,c:0.085}],[0.36,{l:0.9,c:0.028}],[0.62,{l:0.73,c:0.135}],[0.8,{l:0.62,c:0.165}],[0.93,{l:0.87,c:0.16}],[1,{l:0.34,c:0.035}]],At=(t)=>K.find(([e])=>t{let n=At(e),a=v({l:n.l,c:n.c,h:t},J,W);return{bg:{l:0.965,c:0.01,h:t},head:a,eye:a.l>=0.5?{l:0.17,c:0.02,h:t}:{l:0.97,c:0.012,h:t}}},Ht=[["head","bg",1.25],["eye","head",4.5]];function Ft(t,e=!0,n=0){let a=Rt(t,n);if(e)for(let[o,r,s]of Ht)a[o]=v(a[o],a[r],s);return a}function tt(t,e=!0,n=0){let a=Ft(t,e,n),o={};for(let r in a)o[r]=T(a[r]);return o}var y=(t)=>{let e=Math.round(t*100)/100;return Object.is(e,-0)?"0":String(e)};function P({cx:t,cy:e,rx:n,ry:a,n:o=4,rot:r=0}){let s=Math.min(1,(8*Math.pow(2,-1/o)-4)/3),c=n,i=a,u=c*s,m=i*s,l=[[c,0],[c,m],[u,i],[0,i],[-u,i],[-c,m],[-c,0],[-c,-m],[-u,-i],[0,-i],[u,-i],[c,-m],[c,0]],b=r*Math.PI/180,p=Math.cos(b),d=Math.sin(b),g=(x)=>{let[f,M]=l[x];return`${y(t+f*p-M*d)} ${y(e+f*d+M*p)}`},h=`M${g(0)}`;for(let x=1;x<13;x+=3)h+=`C${g(x)} ${g(x+1)} ${g(x+2)}`;return h+"Z"}function et(t,e,n,a,o,r=0){let s=o.length,c=r*Math.PI/180,i=o.map((l,b)=>{let p=c+2*Math.PI*b/s;return[t+n*l*Math.cos(p),e+a*l*Math.sin(p)]}),u=(l)=>i[(l%s+s)%s],m=`M${y(u(0)[0])} ${y(u(0)[1])}`;for(let l=0;l0?r<1?r/2:0.5:0,i=s*Math.PI/180-Math.PI/2,u=Array.from({length:o},(p,d)=>{let g=i+2*Math.PI*d/o;return[t+n*Math.cos(g),e+a*Math.sin(g)]}),m=(p)=>u[(p%o+o)%o],l=(p,d)=>{let[g,h]=m(p),[x,f]=m(d);return`${y(g+(x-g)*c)} ${y(h+(f-h)*c)}`},b=`M${l(0,-1)}`;for(let p=0;p>>19;return t}function jt(t){return t=Math.imul(t^t>>>16,2246822507),t=Math.imul(t^t>>>13,3266489909),(t^t>>>16)>>>0}var at=new TextEncoder;function _t(t){return t.normalize("NFC").trim().toLowerCase()}function st(t,e=!0){let n=e?_t(t):t;return H(1779033703^n.length,at.encode(n))}function F(t,e){return jt(H(H(t,Uint8Array.of(255)),at.encode(e)))/4294967296}function ct(t,e=!0,n){let a=st(t,e),o=(r)=>{let s=n?.[r],c=Array.isArray(s)?s[Math.floor(F(a,r)*s.length)]:s;return c===void 0?F(a,r):c>0?c<1?c:0.999999:0};return o.num=(r,s,c)=>s+o(r)*(c-s),o.int=(r,s,c)=>s+Math.floor(o(r)*(c-s+1)),o.pick=(r,s)=>s[Math.floor(o(r)*s.length)],o.bool=(r,s=0.5)=>o(r)(o(r)*2-1)*s,o}function I(t,e,n){let a=e.expression;if(n||!a)return{l:t,wrap:""};return a.bake(t,a.p)}var j=(t,e)=>e?.tint?e.tint(t,e.p):t,it=(t,e)=>e?`${t}`:t,Ct=(t)=>t.replace(/[&<>]/g,(e)=>e==="&"?"&":e==="<"?"<":">");function O(t,e){let n=ct(t,e.normalize??!0,e.traits);return{t:n,palette:{...tt(e.hue??n.num("hue",0,360),e.contrast??!0,e.tone??n("tone")),...e.palette}}}var Vt=(t)=>t.title?`${Ct(t.title)}`:"";function w(t,e,n){let a=e.background??t.background;if(a===!1)return;return{d:a==="square"?"M0 0H100V100H0Z":P({cx:50,cy:50,rx:50,ry:50,n:a==="circle"?2:6}),fill:n.bg}}var Xt=(t)=>t?``:"";function lt(t){return(e,n={})=>{let{t:a,palette:o}=O(e,n),r=j(o,n.expression),s=n.size?` width="${n.size}" height="${n.size}"`:"",c=I(t.layout(a),n),i=Vt(n)+Xt(w(t,n,r))+it(t.render(c.l,r),c.wrap);return`${i}`}}function ut(t){return(e,n={},a)=>{let{t:o,palette:r}=O(e,n),s=a?.(o,r),c=I(t.layout(o),n,s);return{cls:s?.cls,bg:w(t,n,r),inner:it(t.render(c.l,r,!!s),c.wrap),vars:s?.vars}}}var mt=(t,e,n)=>{let a=e.rx,o=t.num("eye.rx",0.075,0.105)*a,r=t.num("eye.ratio",1.9,3.2),s=t.num("eye.scale",0.78,1.24),c=t.num("eye.stretch",0.85,1.18),i=t.num("eye.gap",0.1,0.24)*a,u=o*Math.max(1,s),m=o*r*Math.max(1,s*c),l=u+a*0.03+i,b=t.jitter("gaze.x",0.09)*n.rx,p=t.num("gaze.y",-0.2,0.08)*n.ry,d=t.jitter("eye.dy",0.04)*n.ry,g=Math.hypot(u,m),h=Math.hypot((Math.abs(b)+l+g)/n.rx,(Math.abs(p)+Math.abs(d)+g)/n.ry),x=h>0.9?0.9/h:1,f=o*x,M=f*r,L=l*x,A=Math.max(0,Math.min(1,i/m)),Lt=Math.min(12,Math.asin(A)*180/Math.PI),X=t.num("eye.lean",-1,1)*Lt,vt=Math.max(-12,Math.min(12,X+t.jitter("eye.lean2",3.5))),Y=n.cx+b*x,z=n.cy+p*x;return[{cx:Y-L,cy:z,rx:f,ry:M,n:t.num("eye.n",3.5,6),rot:X},{cx:Y+L,cy:z+d*x,rx:f*s,ry:M*s*c,n:t.num("eye.n",3.5,6),rot:vt}]};function pt(t,e){let n=(r)=>(t.find(([,s])=>r1+r.jitter(`body.r${b}`,0.16))};s.body?.(r,i);let u=s.face?.(i)??i,m={petals:[],extra:[]};return s.decorate?.(r,i,m),{shape:s.name,draw:s.path,body:i,face:u,petals:m.petals,extra:m.extra,eyes:e(r,i,u)}}function o(r,s,c){let i=(l)=>Math.round(l*100)/100,u=(l,b)=>{let p=``;return c?`${p}`:p},m=``+r.petals.map((l)=>``).join("")+r.extra.map((l)=>``).join("")+``+r.eyes.map(u).join("")+"";return c?`${m}`:m}return{layout:a,render:o,background:!1}}function _(t,e){let n=(o)=>Math.round(o*100)/100,a=e.head;return[...t.petals.map((o)=>({kind:"circle",cx:n(o.cx),cy:n(o.cy),r:n(o.r),fill:a})),...t.extra.map((o)=>({kind:"path",d:o,fill:a})),{kind:"path",d:t.draw?t.draw(t.body):P(t.body),fill:a},...t.eyes.map((o)=>({kind:"path",d:P(o),fill:e.eye}))]}var yt=(t)=>nt(t),bt=(t)=>et(t.cx,t.cy,t.rx,t.ry,t.radii,t.rot),C=(t)=>(e)=>({cx:e.cx,cy:e.cy,rx:e.rx*t,ry:e.ry*t}),ht=(t)=>C(Math.min(...t.radii)*0.95)(t),Yt=(t)=>C(0.84)(t),xt={name:"round",core:1},dt={name:"organic",core:0.98,path:bt,face:ht},gt={name:"boxy",core:0.86,body:(t,e)=>{e.n=t.num("body.n",3.4,6),e.rot=t.num("body.rot",-20,20)}},ft={name:"capsule",core:1.02,body:(t,e)=>{e.ry*=t.num("capsule.squat",0.55,0.68)},face:C(0.94),decorate:(t,e,n)=>{for(let a of[-1,1])n.petals.push({cx:e.cx+a*(e.rx-e.ry),cy:e.cy,r:e.ry})},path:(t)=>rt(t.cx,t.cy,t.rx-t.ry,t.ry)},Mt={name:"nub",core:0.88,decorate:(t,e,n)=>{let a=t.int("nub.n",1,2);for(let o=0;o{let a=t.int("cloud.n",4,6);for(let o=0;o{e.cy+=0.22*e.ry,e.n=2},face:(t)=>({cx:t.cx,cy:t.cy+t.ry*0.05,rx:t.rx*0.88,ry:t.ry*0.88}),decorate:(t,e,n)=>{n.extra.push(ot(e.cx,e.cy,e.rx,e.ry,t.num("droplet.tip",1.4,1.65)))}},Pt={name:"hexagon",core:1.05,path:yt,face:Yt,body:(t,e)=>{e.sides=6,e.rot=t.num("body.rot",-12,12),e.round=t.num("poly.round",0.24,0.5)}},St={name:"sun",core:0.7,decorate:(t,e,n)=>{let a=t.int("sun.n",6,9),o=e.rx*t.num("sun.dist",1,1.08),r=e.rx*t.num("sun.r",0.2,0.26),s=t.num("sun.rot",0,2*Math.PI);for(let c=0;c{e.sides=3,e.rot=t.num("body.rot",-5,5),e.round=t.num("poly.round",0.24,0.5)},face:(t)=>({cx:t.cx,cy:t.cy+t.ry*0.1,rx:t.rx*0.54,ry:t.ry*0.36})};var zt=[[xt,0.22],[dt,0.48],[gt,0.6],[ft,0.7],[Mt,0.79],[kt,0.86],[$t,0.915],[Pt,0.95],[St,0.98],[Ot,1]],k=pt(zt,mt);var Bt=lt(k),Gt=(t,e)=>(n,a)=>{let o=e?e.vars(e.p):{},r=e?.tint?e.tint(a,e.p):a;return{cls:G(t,!!Object.keys(o).length||!!e?.tint),vars:{...N(n),"--mo-head":r.head,"--mo-eye":r.eye,...o}}};function Tt(t,e={}){return ut(k)(t,e,e.animate&&Gt(e.animate,e.expression))}function Me(t,e={}){let{t:n,palette:a}=O(t,e),o=j(a,e.expression),r=I(k.layout(n),e);return{bg:w(k,e,o),transform:r.wrap,marks:_(r.l,o)}}function ke(t,e={}){let{t:n,palette:a}=O(t,e),o=k.layout(n),r=_(o,a),s=e.expression,c=s?.tint?s.tint(a,s.p):null,i=r.length-o.eyes.length;return{bg:w(k,e,a),marks:r.slice(0,i),eyes:r.slice(i),eyeFrames:o.eyes.map(({cx:u,cy:m,rx:l,ry:b,rot:p})=>({cx:u,cy:m,rx:l,ry:b,rot:p})),pose:s?.p,expr:!!s&&(!!Object.keys(s.vars(s.p)).length||!!s.tint),fill:{head:a.head,eye:a.eye},hot:c?{head:c.head,eye:c.eye}:null}}function $e(t,e={}){let{t:n,palette:a}=O(t,e),o=k.layout(n),r=e.expression,s=r?r.bake(o,r.p).l:o;return{palette:r?.tint?r.tint(a,r.p):a,...s}}function wt(t,e){return"data:image/svg+xml,"+Bt(t,e).replace(/"/g,"'").replace(/[%#<>{}|\\^[\]`]/g,(a)=>"%"+a.charCodeAt(0).toString(16).toUpperCase()).replace(/\s+/g," ")}return {render:Tt,svg:Bt,uri:wt}; +})(); +var POSES = (function () { +function O({l:t,c:e,h:s}){let n=s*Math.PI/180,r=e*Math.cos(n),o=e*Math.sin(n),c=t+0.3963377774*r+0.2158037573*o,a=t-0.1055613458*r-0.0638541728*o,l=t-0.0894841775*r-1.291485548*o,y=c*c*c,h=a*a*a,d=l*l*l;return[4.0767416621*y-3.3077115913*h+0.2309699292*d,-1.2684380046*y+2.6097574011*h-0.3413193965*d,-0.0041960863*y-0.7034186147*h+1.707614701*d]}var v=(t)=>t.every((e)=>e>=-0.0001&&e<=1.0001);function S(t){let e=O(t);if(!v(e)){let s=0,n=t.c;for(let r=0;r<12;r++){let o=(s+n)/2;if(v(O({...t,c:o})))s=o;else n=o}e=O({...t,c:s})}return e.map((s)=>Math.min(1,Math.max(0,s)))}function L(t){let[e,s,n]=S(t);return 0.2126*e+0.7152*s+0.0722*n}function k(t,e){let s=L(t),n=L(e);return(Math.max(s,n)+0.05)/(Math.min(s,n)+0.05)}function P(t,e,s){if(k(t,e)>=s)return t;let n=t.l>=e.l?1:-1;for(let c of[n,-n]){let a={...t};for(let l=0;l<60;l++){if(a.l=Math.min(1,Math.max(0,a.l+c*0.02)),k(a,e)>=s)return a;if(a.l===0||a.l===1)break}}let r={...t,l:0,c:0},o={...t,l:1,c:0};return k(r,e)>=k(o,e)?r:o}function m(t){return"#"+S(t).map((e)=>{let s=e<=0.0031308?12.92*e:1.055*Math.pow(e,0.4166666666666667)-0.055;return Math.round(s*255).toString(16).padStart(2,"0")}).join("")}function p(t){let e=parseInt(t.slice(1),16),[s,n,r]=[e>>16&255,e>>8&255,e&255].map((h)=>{let d=h/255;return d<=0.04045?d/12.92:Math.pow((d+0.055)/1.055,2.4)}),o=Math.cbrt(0.4122214708*s+0.5363325363*n+0.0514459929*r),c=Math.cbrt(0.2119034982*s+0.6806995451*n+0.1073969566*r),a=Math.cbrt(0.0883024619*s+0.2817188376*n+0.6299787005*r),l=1.9779984951*o-2.428592205*c+0.4505937099*a,y=0.0259040371*o+0.7827717662*c-0.808675766*a;return{l:0.2104542553*o+0.793617785*c-0.0040720468*a,c:Math.hypot(l,y),h:Math.atan2(y,l)*180/Math.PI}}function N(t,e,s){let n=(h)=>h*Math.PI/180,r=t.c*Math.cos(n(t.h)),o=t.c*Math.sin(n(t.h)),c=e.c*Math.cos(n(e.h)),a=e.c*Math.sin(n(e.h)),l=r+(c-r)*s,y=o+(a-o)*s;return{l:t.l+(e.l-t.l)*s,c:Math.hypot(l,y),h:Math.atan2(y,l)*180/Math.PI}}var f=(t,e,s)=>m(N(p(t),p(e),s));function V(t,e,s){let n="#";for(let r=1;r<7;r+=2){let o=parseInt(t.slice(r,r+2),16),c=parseInt(e.slice(r,r+2),16),a=Math.round(o+(c-o)*s);n+=(a<16?"0":"")+a.toString(16)}return n}var H={h:27,l:0.58,pull:0.6,c:0.18},w={h:358,l:0.72,pull:0.55,c:0.16},A={h:12,l:0.84,pull:0.4,c:0.1},C={h:142,l:0.66,pull:0.6,c:0.13};var R=4.55;function _(t,e,s){let n=p(t),r=p(e),o={l:n.l+(s.l-n.l)*s.pull,c:Math.max(n.c,s.c),h:s.h};o=P(o,B,F);let c=P(r,o,R),a=c.l>=o.l?1:-1,l=m(o);for(let y=0;y<40;y++){let h=m(c),d=1/0;for(let g=0;g<=10;g++){let T=g/10;d=Math.min(d,k(p(f(e,h,T)),p(f(t,l,T))))}if(d>=R)return[l,h];let E=Math.min(1,Math.max(0,c.l+a*0.02));if(E===c.l)return[l,h];c={...c,l:E}}return[l,m(c)]}var I=[[0.2,{l:0.86,c:0.085}],[0.36,{l:0.9,c:0.028}],[0.62,{l:0.73,c:0.135}],[0.8,{l:0.62,c:0.165}],[0.93,{l:0.87,c:0.16}],[1,{l:0.34,c:0.035}]],K=(t)=>I.find(([e])=>t{let s=K(e),n=P({l:s.l,c:s.c,h:t},B,F);return{bg:{l:0.965,c:0.01,h:t},head:n,eye:n.l>=0.5?{l:0.17,c:0.02,h:t}:{l:0.97,c:0.012,h:t}}},D=[["head","bg",1.25],["eye","head",4.5]];function j(t,e=!0,s=0){let n=U(t,s);if(e)for(let[r,o,c]of D)n[r]=P(n[r],n[o],c);return n}function W(t,e=!0,s=0){let n=j(t,e,s),r={};for(let o in n)r[o]=m(n[o]);return r}var b={esx:1,esy:1,tilt:0,edy:0,edx:0,esx2:0,esy2:0,tilt2:0,edy2:0,lock:0,heat:0,shake:0,rock:0,bdy:0},u=(t)=>String(Math.round(t*1000)/1000);function i(t,e){return{l:{...t,eyes:t.eyes.map((s,n)=>({...s,cx:s.cx+e.edx*(n?1:-1),cy:s.cy+e.edy+(n?e.edy2:0),rx:s.rx*(e.esx+(n?e.esx2:0)),ry:s.ry*(e.esy+(n?e.esy2:0)),rot:s.rot*(1-e.lock)+(e.tilt+(n?e.tilt2:0))*(n?1:-1)}))},wrap:e.bdy!==0?`translate(0 ${u(e.bdy)})`:""}}function z(t,e,s=1){return{eyes:t.eyes.map((n,r)=>{let o=r?1:-1,c=r?1:0,a=n.rot,l=c*(1-e.rock)+e.rock*((1+o*s)/2);return`translate(${u(n.cx+e.edx*o)} ${u(n.cy+e.edy+l*e.edy2)}) rotate(${u((e.tilt+c*e.tilt2)*o+a*(1-e.lock))}) scale(${u(e.esx+c*e.esx2)} ${u(e.esy+c*e.esy2)}) rotate(${u(-a)}) translate(${u(-n.cx)} ${u(-n.cy)})`}),wrap:`translate(0 ${u(e.bdy)})`}}function J(t,e,s){let n=t??b,r=e??b,o={};for(let c in b){let a=c;o[a]=n[a]*(1-s)+r[a]*s}return o}function x(t){let e={};for(let s in b){if(s==="heat")continue;let n=t[s];if(n!==b[s])e["--mo-"+s]=u(n)}return e}function M(t,e,s){let[n,r]=_(t.head,t.eye,s);return{...t,head:f(t.head,n,e.heat),eye:f(t.eye,r,e.heat)}}var G=(t,e)=>M(t,e,H),Z={p:b,vars:x,bake:i},ee={p:{esx:1.72,esy:0.3,tilt:8,edy:-1.5,edx:1.5,esx2:0.08,esy2:0.05,tilt2:-16,edy2:0,lock:1,heat:0,shake:0,rock:0,bdy:-2.2},vars:x,bake:i},te={p:{esx:0.6,esy:0.56,tilt:26,edy:3.6,edx:1.9,esx2:-0.05,esy2:-0.07,tilt2:-7,edy2:0,lock:1,heat:0,shake:0,rock:0,bdy:2.6},vars:x,bake:i},se={p:{esx:1.85,esy:0.26,tilt:-33,edy:0.4,edx:0.6,esx2:0,esy2:-0.03,tilt2:5,edy2:0,lock:1,heat:0.62,shake:0.55,rock:0,bdy:0.8},vars:x,bake:i,tint:G},ne={p:{esx:1.34,esy:1.2,tilt:-6,edy:-1.05,edx:0.5,esx2:0.05,esy2:0.07,tilt2:3,edy2:0,lock:1,heat:0,shake:0,rock:0,bdy:-1.4},vars:x,bake:i},re={p:{esx:1.32,esy:0.76,tilt:5,edy:-0.6,edx:0.8,esx2:0.26,esy2:-0.56,tilt2:-11,edy2:0,lock:1,heat:0,shake:0,rock:0,bdy:-1.1},vars:x,bake:i},oe={p:{esx:1.14,esy:0.22,tilt:0,edy:2.4,edx:0.3,esx2:-0.04,esy2:0.03,tilt2:4,edy2:0,lock:1,heat:0,shake:0,rock:0,bdy:1.2},vars:x,bake:i},ce={p:{esx:1.3,esy:0.42,tilt:18,edy:-0.5,edx:0.5,esx2:0.06,esy2:-0.06,tilt2:-36,edy2:0,lock:1,heat:0,shake:0,rock:0,bdy:-1},vars:x,bake:i},ae={p:{esx:0.95,esy:1.02,tilt:4,edy:-0.2,edx:0.3,esx2:0.24,esy2:-0.44,tilt2:-18,edy2:0,lock:1,heat:0,shake:0,rock:0,bdy:0},vars:x,bake:i},le={p:{esx:0.78,esy:0.96,tilt:-12,edy:-1.5,edx:-0.8,esx2:-0.04,esy2:0.05,tilt2:4,edy2:0,lock:1,heat:0,shake:0.35,rock:0,bdy:-0.6},vars:x,bake:i},ie={p:{esx:0.86,esy:1.28,tilt:-14,edy:-0.5,edx:-0.35,esx2:0.05,esy2:0.06,tilt2:6,edy2:0,lock:1,heat:0.6,shake:0,rock:0,bdy:-1.6},vars:x,bake:i,tint:(t,e)=>M(t,e,w)},xe={p:{esx:0.62,esy:0.5,tilt:10,edy:1.4,edx:-0.2,esx2:-0.05,esy2:-0.04,tilt2:-8,edy2:0,lock:1,heat:0.55,shake:0,rock:0,bdy:0.9},vars:x,bake:i,tint:(t,e)=>M(t,e,A)},ye={p:{esx:1.25,esy:0.34,tilt:20,edy:1.8,edx:0.8,esx2:0.05,esy2:-0.05,tilt2:-6,edy2:0,lock:1,heat:0.6,shake:0.18,rock:0,bdy:1.4},vars:x,bake:i,tint:(t,e)=>M(t,e,C)},ue={p:{esx:1.15,esy:0.62,tilt:0,edy:4.2,edx:0.4,esx2:0.02,esy2:0.06,tilt2:0,edy2:-8.4,lock:1,heat:0,shake:0,rock:0.8,bdy:-0.4},vars:x,bake:i};return {bakePose:i,happy:ee,heatTint:G,idle:Z,love:ie,mad:se,poseVars:x,sad:te,scared:le,shy:xe,sick:ye,sleepy:oe,smug:ce,surprised:ne,thinking:ue,tintWith:M,unsure:ae,wink:re}; +})(); + /* ---- src/shared/puck.ts, the parts a face needs, line for line ---- */ + var PUCK_SHAPES = ['auto', 'round', 'organic', 'boxy', 'nub', 'cloud', 'sun', 'capsule', 'triangle', 'hexagon', 'droplet']; + var PUCK_SHAPE_TRAIT = { round: 0.11, organic: 0.35, boxy: 0.54, capsule: 0.65, nub: 0.745, cloud: 0.825, droplet: 0.8875, hexagon: 0.9325, sun: 0.965, triangle: 0.99 }; + var PUCK_EXPRESSIONS = ['idle', 'happy', 'sad', 'mad', 'surprised', 'wink', 'sleepy', 'smug', 'unsure', 'scared', 'love', 'shy', 'sick', 'thinking']; + var PUCK_DEFAULT_COLOR = '#FFC94F'; + var PUCK_SWATCHES = [ + { id: 'cream', hex: '#FFF8E7', group: 'pastel', name: 'Off white' }, { id: 'grey', hex: '#D9D3DE', group: 'pastel', name: 'Grey' }, + { id: 'red', hex: '#F5B3AB', group: 'pastel', name: 'Red' }, { id: 'blue', hex: '#B3D4F0', group: 'pastel', name: 'Blue' }, + { id: 'green', hex: '#B4DEC3', group: 'pastel', name: 'Green' }, { id: 'yellow', hex: PUCK_DEFAULT_COLOR, group: 'pastel', name: 'Yellow' }, + { id: 'coral', hex: '#D96A62', group: 'vibrant', name: 'Coral' }, { id: 'peach', hex: '#D99168', group: 'vibrant', name: 'Peach' }, + { id: 'lemon', hex: '#DCAB3C', group: 'vibrant', name: 'Lemon' }, { id: 'mint', hex: '#5CA97A', group: 'vibrant', name: 'Mint' }, + { id: 'sky', hex: '#4F9FAF', group: 'vibrant', name: 'Sky' }, { id: 'lilac', hex: '#9482D3', group: 'vibrant', name: 'Lilac' } + ]; + var SHAPE_NAME = { auto: 'Auto', round: 'Round', organic: 'Organic', boxy: 'Boxy', nub: 'Nub', cloud: 'Cloud', sun: 'Sun', capsule: 'Capsule', triangle: 'Triangle', hexagon: 'Hexagon', droplet: 'Droplet' }; + var EXPR_NAME = { idle: 'Idle', happy: 'Happy', sad: 'Sad', mad: 'Mad', surprised: 'Surprised', wink: 'Wink', sleepy: 'Sleepy', smug: 'Smug', unsure: 'Unsure', scared: 'Scared', love: 'In love', shy: 'Shy', sick: 'Sick', thinking: 'Thinking' }; + var SHUFFLE_WORDS = ['comet', 'pebble', 'lantern', 'otter', 'maple', 'ember', 'harbour', 'juniper', 'marble', 'nimbus', 'orchid', 'quill', 'saffron', 'tundra', 'velvet', 'willow', 'zephyr', 'cobalt', 'dune', 'fjord', 'glacier', 'heron', 'indigo', 'kestrel']; + function normalizeHex(v) { + if (typeof v !== 'string') return null; + var s = v.trim().replace(/^#/, ''); + if (/^[0-9a-fA-F]{6}$/.test(s)) return '#' + s.toUpperCase(); + if (/^[0-9a-fA-F]{3}$/.test(s)) return '#' + s.split('').map(function (c) { return c + c; }).join('').toUpperCase(); + return null; + } + function linear(c) { var v = c / 255; return v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4); } + function rgbOf(hex) { var h = normalizeHex(hex) || PUCK_DEFAULT_COLOR; return [1, 3, 5].map(function (i) { return parseInt(h.slice(i, i + 2), 16); }); } + function luminance(hex) { var c = rgbOf(hex).map(linear); return 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2]; } + function oklchHue(hex) { + var c = rgbOf(hex).map(linear), r = c[0], g = c[1], b = c[2]; + var l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b); + var m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b); + var s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b); + var A = 1.9779984951 * l - 2.4285922050 * m + 0.4505937099 * s; + var B = 0.0259040371 * l + 0.7827717662 * m - 0.8086757660 * s; + return ((Math.atan2(B, A) * 180) / Math.PI + 360) % 360; + } + var EYE_FLIP = 0.18; + function bodyLook(hex) { return { hue: Math.round(oklchHue(hex)), tone: luminance(hex) > EYE_FLIP ? 0 : 0.999 }; } + + /* ---- PuckFace.tsx, ported: the colour wins over the pose, dark eyes on a light body ---- */ + function untinted(e) { var o = {}; for (var k in e) o[k] = e[k]; o.tint = undefined; return o; } + var POSE = {}; PUCK_EXPRESSIONS.forEach(function (x) { POSE[x] = untinted(POSES[x]); }); + function faceOpts(look, size, animate) { + var body = normalizeHex(look.color) || PUCK_DEFAULT_COLOR, bl = bodyLook(body); + var o = { size: size, background: false, hue: bl.hue, tone: bl.tone, palette: { head: body }, expression: POSE[look.expression] || POSE.idle }; + if (look.shape !== 'auto' && PUCK_SHAPE_TRAIT[look.shape]) o.traits = { shape: PUCK_SHAPE_TRAIT[look.shape] }; + if (look.traits) { o.traits = o.traits || {}; for (var tk in look.traits) o.traits[tk] = look.traits[tk]; } // traits, as the app's PuckLook carries them + if (animate) o.animate = animate; + return o; + } + function faceSVG(look, size, animate) { + if (!animate) return BLOB.svg(look.seed, faceOpts(look, size)); + var h = BLOB.render(look.seed, faceOpts(look, size, animate)), vars = []; + for (var k in h.vars) vars.push(k + ':' + h.vars[k]); + return ''; + } + function face(look, size, shadow, animate) { + return ''; + } + + /* ---- the one interactive Stapler: state, disc, ring (shared/puck.ts RING and RING_ANGLE, puck.css motion) ---- + STAPLER_EXPRESSION and STAPLER_COLOR are how Stapler arrives before anyone touches the controls. The founder + picked them by POSITION in the two rows, so they are written here as positions rather than as loose strings: + 'surprised' is the fifth chip in PUCK_EXPRESSIONS, '#B3D4F0' is the fourth dot in PUCK_SWATCHES (Blue, the + fourth of the six pastels, which render first). Reorder either array and these stop meaning what he asked for. + Deliberately NOT PUCK_DEFAULT_COLOR: that constant is also the yellow swatch's own hex, so pointing it at blue + would leave the palette with two blues and no yellow. Used in both places Stapler appears at rest, the + customiser disc and the Pro feature card, so the same character is never two colours on one page. */ + var STAPLER_EXPRESSION = 'surprised'; + var STAPLER_COLOR = '#B3D4F0'; + var look = { seed: 'stapler', shape: 'round', expression: STAPLER_EXPRESSION, color: STAPLER_COLOR }; + var RING = { radius: 1.1, button: 0.62, closeRadius: 1.0, closeButton: 0.42 }; + var RING_ANGLE = { screenshot: 0, meeting: 60, invisible: 130, computerUse: 230, message: 300 }; + var RING_LABEL = { screenshot: 'Screenshot', message: 'Record message', meeting: 'Record meeting', invisible: 'Leave invisible', computerUse: 'Computer use', close: 'Close' }; + var RING_SUB = { computerUse: 'Coming soon' }; + var ICON = { screenshot: 'screenshot', message: 'mic', meeting: 'video', invisible: 'eye', computerUse: 'computer' }; + var GLYPH = { + screenshot: 'M4 8V5a1 1 0 011-1h3M16 4h3a1 1 0 011 1v3M20 16v3a1 1 0 01-1 1h-3M8 20H5a1 1 0 01-1-1v-3M9 12h6', + mic: 'M9 3h6v8a3 3 0 01-6 0zM5 11a7 7 0 0014 0M12 18v3M9 21h6', + video: 'M3 7a1 1 0 011-1h11a1 1 0 011 1v10a1 1 0 01-1 1H4a1 1 0 01-1-1zM16 10l5-3v10l-5-3', + eye: 'M3 12s3.5-6 9-6 9 6 9 6-3.5 6-9 6-9-6-9-6zM12 12m-2.5 0a2.5 2.5 0 105 0a2.5 2.5 0 10-5 0', + computer: 'M3 5h18v11H3zM8 20h8M12 16v4', + close: 'M6 6l12 12M18 6L6 18' + }; + function glyph(name, px) { return ''; } + function labelSide(angle) { var rad = angle * Math.PI / 180, s = Math.sin(rad); if (s >= 0.35) return 'right'; if (s <= -0.35) return 'left'; return Math.cos(rad) >= 0 ? 'up' : 'down'; } + + return { face: face, faceSVG: faceSVG, PUCK_SWATCHES: PUCK_SWATCHES, PUCK_EXPRESSIONS: PUCK_EXPRESSIONS, PUCK_SHAPES: PUCK_SHAPES, SHUFFLE_WORDS: SHUFFLE_WORDS, RING: RING, RING_ANGLE: RING_ANGLE, RING_LABEL: RING_LABEL, RING_SUB: RING_SUB, ICON: ICON, glyph: glyph, labelSide: labelSide, normalizeHex: normalizeHex }; +})(); diff --git a/docs/index.html b/docs/index.html index a8d4c372a3..2d4277cae9 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1,5 +1,5 @@ - - + + @@ -15,1703 +15,1449 @@ gtag('config', 'AW-18388216863'); } - - -Munder Difflin · Multi agent harness for Claude Code, Codex and ten more - + +Munder Difflin · Coolest app to use agents + + + - - - - + + + + - - - - - - - - - - - - + + + + + + + + + + + + + - - - + - - - - - -
+
+ - -
- - - +
+ +
-
- - - -
-
-
- 100K+downloads - 6.7KGitHub stars - #1GitHub Trending - #5Product Hunt - 1M+Reddit views - 5M+Instagram and X +
+ + +
- - - -
-
-
-

Terminal agents got really good. Then you opened six terminals.

-

Claude Code in one tab. Codex in another. A third one has been waiting on you for twenty minutes. Every agent is brilliant alone, and nobody is watching the whole room.

-

Munder Difflin gives them a floor, a boss and a memory.

+ +
+ +
+
+ 01Word of mouth +

Don't just take our word.

+

Here's what people say about us.

- +
+
“It's whimsical and fun, and it's better if more things like this exist in the world than not.”
Hacker News
“This might be the most fun way to run a multi-agent coding setup.”
@codvyn

“Pass this to any dev who uses Claude Code or Codex”

Pásale esto a cualquier dev que use Claude Code o Codex
@SoyKokii
“This is insane execution!”
r/ClaudeCode

“Meet Munder Difflin, a free tool that lets you manage all your AI agents”

Te presento Munder Difflin, herramienta gratuita que te permitirá gestionar todos tus agentes de IA
@alecelis.cl
“So COOL...someone just built THE OFFICE theme agent harness”
Shubham Saboo
“This is super cute.”
Hacker News

“The weirdest and smartest tool I saw this week: an office like The Office, but every employee is AI”

أغرب وأذكى أداة شفتها هالأسبوع: مكتب The Office بس الموظفين كلهم AI
@Red1OneX
“rimworld for agents”
r/ClaudeAI
“What if your entire workflow ran itself while you were offline?”
@thecuriousvishal
“As a big fan of The Office, this might be my favorite take on an agent harness yet”
@this.is.immo
“This looks like a tool from Cowboy Bebop.”
Hacker News

“Not just a tool that looks fun: it is a system for running several coding agents together”

ただの見た目が面白いツールではなく、複数のコードエージェントをまとめて動かすための仕組みになってる
@codexneo4
“Can't wait to meet the Assistant to the Manager.”
r/ClaudeAI

“Set up a whole office of AI staff who work for you without a break”

나를 위해 쉬지 않고 일하는 AI 직원, 사무실째로 차려보세요.
@sdk.kim.builds
+
“The next steps in innovation will occur at the harness level. Why not have some fun while we’re at it?”
Hacker News
“Someone on Reddit built an app that lets you clone yourself to run your computer while you're away.”
@VaibhavSisinty
“That's awesome, I really like the UI for handling them.”
r/ClaudeCode
“Your AI agents just got their own virtual office.”
@sadhik.ai
“Definitely one to watch if you're experimenting with multi-agent workflows.”
Dipanshu Kushwaha
“don't let the crankles get you down. this thing is delightful.”
Hacker News

“This project is quite interesting: it turns AI agents straight into a virtual office”

这个项目有点意思,直接把 AI Agent 做成了一间虚拟办公室
@xiaomovps
“Nice, the character art reminds me of Earthbound”
r/ClaudeCode

“Munder Difflin sets out to organise that work in a visual office”

O Munder Difflin propõe organizar esse trabalho em um escritório visual.
@ana.natech
“For anyone already paying for Claude Code or Codex and wishing one terminal could become ten coordinated ones, this is the most charming way to find out if that works.”
The Menon Lab
“More software should aim to be both useful and fun.”
Hacker News

“The most appealing way to manage agents I have seen”

جذاب‌ترین روشی بود که برای مدیریت ایجنت‌ها دیدم.
@mahdihomeyli
“dammit. This is pretty cool.”
r/ClaudeAI
“This is worth saving.”
@ai.git

“Above all, it is cute. And yet it performs well too.”

なにはともあれ、可愛い。なのに性能的にも良い。
note.com
+
- -
-
-

Twelve terminal agents. One office.

-

Every clone is a real CLI agent in a real terminal, on the plan you already have. Nothing to switch, nothing new to pay for.

-
-
on it
Claude Code
-
Codex
-
Grok
-
tests green
Kimi Code
-
Antigravity
-
Qwen
-
PR open
Gemini CLI
-
OpenCode
-
Crush
-
reviewing
Pi
-
Copilot
-
Cursor
+ +
+
+
+
+ 01Your office +

Hire a team. Give everyone a face.

+

Start from 36 role templates or write your own. Each agent gets a name, a face and the model it runs on, then a desk in your office. Add, swap or rename them any time.

+
+
+
+
Your officeHire someone on the right
+
+
+
+
Add an agent36 templates
+
+ +
FaceShuffle for a new one
+
Role

+
Engine
+
+
Uses the plan you signed in with
-

New models reach you without a new version: the model pickers read the list at launch. Opus 5.5 is the default Claude model, and Haiku 5.5 is in the list.

- -
-
-
-

One orchestrator. Agents that hand work to each other.

-

Give Michael, your orchestrator, one sentence. He breaks it into tasks, picks the right clone for each, and they pass work along without you in the middle. You only hear about what needs you, in one Ask me box.

-
    -
  • A task board every clone can read
  • -
  • An inbox per agent, and Ask me for questions that need a human
  • -
  • A circuit breaker that stops runaway spend
  • -
  • Crashed agents restart in the same folder, work intact
  • -
-
-
-
-
-
- © 2026 Munder Difflin. Free and open source. - + +
+
+
+ 12FAQ +

Questions, answered.

+

Thirteen short answers to what people ask before they download. Open any question to read its answer.

+

Ask us on DiscordSee plans and pricing

- -

The pixel art tilesets in the app are Modern Interiors by LimeZu, used under the Complete Version licence. The Office cast is drawn by the app itself. Stapler's face is a Blobatar, MIT. Munder Difflin is an affectionate parody and is not affiliated with NBC's The Office or Dunder Mifflin.

+
    +
  • What is Munder Difflin?

    An app that runs a team of AI agents on your computer, as an office you can watch. You hire agents, give each one a role, a face and a model, and hand them work in plain words. They plan it, do it and report back.

  • +
  • Is it only for code?

    No. Agents take on any knowledge work you can describe: research, writing, design, planning, support and operations, and code too. Start from a role template or write your own.

  • +
  • Which AI models does it work with?

    Twelve engines: Claude Code, Codex, Gemini CLI, Grok, Kimi Code, Antigravity, Qwen, OpenCode, Crush, Pi, Copilot and Cursor, plus a custom command of your own. Each agent runs on its own engine, so one office can mix them.

  • +
  • Do I need my own subscription or API key?

    Yes. We do not sell model access. Your agents run on the plans you already pay for, like Claude, ChatGPT or Gemini, through each engine's own sign in. Engines that take an API key, such as OpenCode, Crush and Pi, get it once in Settings, and it stays on your computer.

  • +
  • Where do my work and data live?

    On your computer. Agents, memory and tasks are plain files in a folder you can open, back up or delete. Each engine talks to its own provider, as it does on its own. The app sends us anonymous usage counts, never your prompts, agent output or file paths. What we collect.

  • +
  • What is free, and what does Pro add?

    The app is free, with as many agents as you like in the Classic office. Pro adds the Pro workspace: an orchestrator that runs your other agents, a task board, an inbox, automations, memory, Stapler, temps and the capabilities catalog. The free plan lets you use Pro features up to a monthly allowance of work tokens. Plans and prices are on the pricing page.

  • +
  • Is it open source?

    The free app is: MIT licensed, on GitHub, so you can read it, build it and change it. Pro is not open source.

  • +
  • How do I keep spend in check?

    Two ways. With Pro, set token limits for the whole office and for each agent, every 5 hours, day, week or month. In every version, a circuit breaker watches every agent for loops, error storms and runaway spend. It first tells the agent to sum up and change approach, then cuts its budget, and stops it if you allow that.

  • +
  • What is a Cloud Office?

    A second office on a computer we run for you, so agents keep working while your laptop sleeps. It is separate from your computer and nothing syncs. Its agents can message the ones on your computer, and you share a project through git. It runs as many agents as your machine fits. It comes with Max, and we are opening it one account at a time.

  • +
  • Does it work with Slack and my other tools?

    Yes. Send work from a Slack thread and the answer comes back in the same thread. Webhooks from GitHub, Linear, Telegram or your own service can start work on their own. Agents can plug into over 2,000 MCP servers, skills, plugins and tools, or anything with an API. Some of this needs Pro; the pricing page says which.

  • +
  • Can I change how my office looks?

    Yes. Pick a simulation: Munder Difflin, Star Hike, One Fragment, Toy Tale, Cyber Dunk, Off-white House, South of Market or Hurry Porter, or Minimal when you only want the work and the spend. With Pro, say what you want and an agent builds it, or import a theme someone shared as a zip.

  • +
  • Can my team use it?

    Yes, with Teams. Every seat gets Pro, teammates join with an invite code from the admin, and their agents talk to each other, share files and pass work. Talk to us about Teams.

  • +
  • Which computers does it run on?

    Mac, with Apple silicon or Intel, and 64 bit Windows and Linux. There is no phone app yet; it is on the way. Until then, send work from your phone through Slack.

  • +
-
- - + + - /* ---- The Teams size slider and its running total went with the figures on 16 Sep 2026. Teams is arranged on a call, so there is nothing on - this page left to compute. ---- */ + +
+
+ 10Good morning +

Your office is open.

+

Download Munder Difflin, hire your first agent and give it something to do. It will be at its desk when you come back.

+
+ Download for Mac + Windows + Linux + Sign in + +
+ +
+ +
+
+ - /* ---- Font self check: the three app fonts are inlined under unique names, so a failed load cannot be masked by a system Inter. ---- */ - if (document.fonts && document.fonts.ready) document.fonts.ready.then(function () { - var want = ['MD Inter', 'MD Mono', 'MD Pixel'], loaded = 0; - document.fonts.forEach(function (f) { if (want.indexOf(f.family.replace(/"/g, '')) >= 0 && f.status === 'loaded') loaded++; }); - var el = document.getElementById('fontCheck'); - if (el) el.textContent = 'Fonts: ' + loaded + ' of 3 app fonts loaded' + (loaded < 3 ? ', the rest fell back.' : '.'); - }); + - /* ---- Live star count --------------------------------------------------- - Every .starct is WRITTEN CORRECT in the markup (6.7K, GitHub API 2026-09-09 - said 6,686). This only overwrites it when a real number comes back. - The unauthenticated GitHub API allows 60 requests an hour per IP, so under - real traffic a share of visitors WILL be rate limited with a 403. That is - the expected case, not the error case: they must keep seeing the written - number. So there is no spinner, no dash, no zero and no error state, and - nothing here runs before paint. Every failure path is a silent return. ---- */ - (function () { - var els = document.querySelectorAll('.starct'); - if (!els.length || typeof fetch !== 'function') return; - function short(n) { - if (n < 1000) return String(n); - var k = n / 1000; - return (k >= 100 ? Math.round(k) : k.toFixed(1).replace(/\.0$/, '')) + 'K'; - } - try { - fetch('https://api.github.com/repos/chaitanyagiri/munder-difflin', { headers: { 'Accept': 'application/vnd.github+json' } }) - .then(function (r) { return r.ok ? r.json() : null; }) - .then(function (d) { - var n = d && d.stargazers_count; - if (typeof n !== 'number' || !isFinite(n) || n <= 0) return; - var t = short(Math.floor(n)); - for (var i = 0; i < els.length; i++) els[i].textContent = t; - }) - .catch(function () { }); - } catch (e) { } - })(); -})(); - +
+
Psst. In the app I float over every window.
+ + +
+ +
On your phone?Munder Difflin runs on your computer.
+ +
+ + + + + + - - - - diff --git a/docs/media/og-landing.png b/docs/media/og-landing.png new file mode 100644 index 0000000000..2968bb3dc9 Binary files /dev/null and b/docs/media/og-landing.png differ