Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

Improve FF1 pairing panel responsive layout

- Add horizontal left/right layout for wider panels (≥420px)
- Add close button to pairing overlay
- Make pairing overlay cover panel instead of pushing content
- Update instructions with clearer 3-step process
- Add container queries for various panel sizes
- Smaller QR code at narrow widths, larger at spacious sizes

+228 -73
+6 -6
.devcontainer/config.fish
··· 1379 1379 echo "" # Add space after sixel in eat 1380 1380 end 1381 1381 1382 - # Full aesthetic platform restart (emacs + reconnect llm) 1382 + # Full aesthetic platform restart (emacs + reconnect artery) 1383 1383 function ac-restart 1384 1384 echo "🔄 Full aesthetic platform restart..." 1385 1385 ac-emacs-restart 1386 1386 if test $status -eq 0 1387 - echo "🤖 Reconnecting to llm..." 1388 - emacsclient -nw -c --eval '(aesthetic-backend (quote "llm"))' 1387 + echo "🩸 Reconnecting to artery..." 1388 + emacsclient -nw -c --eval '(aesthetic-backend (quote "artery"))' 1389 1389 end 1390 1390 end 1391 1391 ··· 1505 1505 1506 1506 # For fast config reloading - simple approach 1507 1507 function platform 1508 - emacsclient -nw -c --eval '(aesthetic-backend (quote "llm"))' 1508 + emacsclient -nw -c --eval '(aesthetic-backend (quote "artery"))' 1509 1509 end 1510 1510 1511 1511 function ensure-emacs-daemon-ready ··· 1707 1707 1708 1708 # Connect to emacs and run aesthetic-backend to create all tabs 1709 1709 echo "🚀 Connecting to aesthetic platform..." 1710 - emacsclient -nw -c --eval '(aesthetic-backend (quote "llm"))' 1710 + emacsclient -nw -c --eval '(aesthetic-backend (quote "artery"))' 1711 1711 set -l exit_code $status 1712 1712 if test $exit_code -ne 0 1713 1713 echo "" ··· 1733 1733 end 1734 1734 1735 1735 # Connect to emacs with aesthetic-backend 1736 - emacsclient -nw -c --eval '(aesthetic-backend (quote "llm"))' 1736 + emacsclient -nw -c --eval '(aesthetic-backend (quote "artery"))' 1737 1737 end 1738 1738 1739 1739 # TODO: Automatically kill online mode and go to offline mode if necessary.
+2 -2
ac-electron/main.js
··· 1402 1402 1403 1403 console.log('[main] Starting PTY with size:', cols, 'x', rows); 1404 1404 writeEmacsLog(`PTY size: ${cols}x${rows}\n`); 1405 - writeEmacsLog(`Command: emacsclient -nw -c --eval "(aesthetic-backend 'llm)"\n`); 1405 + writeEmacsLog(`Command: emacsclient -nw -c --eval "(aesthetic-backend 'artery)"\n`); 1406 1406 1407 1407 // Connect PTY - run aesthetic-backend to set up tabs 1408 1408 ptyProcessFor3D = pty.spawn(dockerPath, [ 1409 1409 'exec', '-it', '-e', 'LANG=en_US.UTF-8', '-e', 'LC_ALL=en_US.UTF-8', 1410 1410 '-e', `COLUMNS=${cols}`, '-e', `LINES=${rows}`, 1411 1411 'aesthetic', 1412 - 'emacsclient', '-nw', '-c', '--eval', "(aesthetic-backend 'llm)" 1412 + 'emacsclient', '-nw', '-c', '--eval', "(aesthetic-backend 'artery)" 1413 1413 ], { 1414 1414 name: 'xterm-256color', 1415 1415 cols: cols,
+5 -3
system/netlify/functions/index.mjs
··· 987 987 var SYN=isKidlisp?(isLightMode?{kw:[180,100,20],fn:[160,120,20],str:[180,80,40],num:[40,140,40],cmt:[80,130,60],op:[80,60,40],tp:[180,120,40],vr:[140,100,20]}:{kw:[255,180,80],fn:[255,220,100],str:[255,160,120],num:[180,255,180],cmt:[120,180,120],op:[240,240,200],tp:[255,200,120],vr:[255,230,150]}):(isLightMode?{kw:[107,43,107],fn:[0,100,0],str:[139,69,19],num:[0,100,0],cmt:[107,142,35],op:[40,30,90],tp:[0,128,128],vr:[56,122,223]}:{kw:[197,134,192],fn:[220,220,170],str:[206,145,120],num:[181,206,168],cmt:[106,153,85],op:[212,212,212],tp:[78,201,176],vr:[156,220,254]}); 988 988 var COLS={'.mjs':isLightMode?'#e8dcc8':'#1e3a5f','.js':isLightMode?'#dce8d0':'#2d4a3e','.lisp':isKidlisp?(isLightMode?'#e0d0a0':'#3d3020'):(isLightMode?'#e8d8e0':'#3d2a4a'),default:isKidlisp?(isLightMode?'#f0e8d0':'#2a2010'):(isLightMode?'#f5ebe0':'#1a2433')}; 989 989 var PAGECOLS=isKidlisp?(isLightMode?['#f5e8d0','#f0e0c0','#f5e0d0','#f0e8c8','#f5e4c8','#f0dcc0','#f5e0c8','#f0e4d0']:['#2d1a0d','#3d2810','#2d2010','#3d3010','#2d2a10','#3d2a08','#2d1810','#3d2010']):(isLightMode?['#fcf7c5','#f5f0c0','#fffacd','#f5ebe0','#e8e3b0','#f0ebd0','#e8dcc8','#fcf5c8']:['#0d1a2d','#0d2d1a','#2d0d1a','#1a0d2d','#1a2d0d','#2d1a0d','#0d1a1a','#1a1a2d']); 990 - var files=[],scrollYs={},bootStart=performance.now(),motd='',motdHandle='',motdStart=0; 990 + var files=[],scrollYs={},bootStart=performance.now(),motd='',motdHandle='',motdStart=0,pgStart=null; 991 991 var lines=[],lc=0,mL=10,lastLog=performance.now(),lb=0,bp=0; 992 - var tinyPng='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsTAAALEwEAmpwYAAABWUlEQVR4nN2Ru0pDQRCGg3fUzk5bEVS0ELxVwQsYTRBvJ5ed3VgZVBB8g1Q+gCDIzq6IluclUktmT1Kk8mWUWT2RTcBep1nmZ//vn53NZP5XOWU3ndTlOIoHe/UE8MxJM8s9SdxuVR9nAnMneph0Up+25esECSwEAGFK3gh4SwIvnDQ5UrgXAN5v7kcJ9HUjWx/iKZwyJ6RsxMkEWPzSTY7A5psVsxaYG+fPYyTNFZ8+qaaHSRjhzcpGrSou+knKdprH70t2Ci+Z7qS56wIAj9M7DPkJskcBIAG96gHKzKdv/X5vkU+/E7B5AtxI4GnBKasSpXf5brBIqulxB/qQjZzO4/uxwe68qZep7l6EXmKNQemv+GIzE5vKHKTL87owpbbCOQK9wj1Jvc59prcSwAqnd6J4hM0pgGGsf9TrA32mACD0Pu/Dp1bNcqJ01gMEFkjYrV/Nf7s+AS1XxDy7PXOrAAAAAElFTkSuQmCC',img=new Image(),imgFull=new Image(),imgFullLoaded=!1;img.src=tinyPng;imgFull.onload=function(){imgFullLoaded=!0};imgFull.src='/purple-pals.svg'; 993 - var uH=null,hST=0,run=true,f=0,netAct=0; 992 + var tinyPng='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsTAAALEwEAmpwYAAABWUlEQVR4nN2Ru0pDQRCGg3fUzk5bEVS0ELxVwQsYTRBvJ5ed3VgZVBB8g1Q+gCDIzq6IluclUktmT1Kk8mWUWT2RTcBep1nmZ//vn53NZP5XOWU3ndTlOIoHe/UE8MxJM8s9SdxuVR9nAnMneph0Up+25esECSwEAGFK3gh4SwIvnDQ5UrgXAN5v7kcJ9HUjWx/iKZwyJ6RsxMkEWPzSTY7A5psVsxaYG+fPYyTNFZ8+qaaHSRjhzcpGrSou+knKdprH70t2Ci+Z7qS56wIAj9M7DPkJskcBIAG96gHKzKdv/X5vkU+/E7B5AtxI4GnBKasSpXf5brBIqulxB/qQjZzO4/uxwe68qZep7l6EXmKNQemv+GIzE5vKHKTL87owpbbCOQK9wj1Jvc59prcSwAqnd6J4hM0pgGGsf9TrA32mACD0Pu/Dp1bNcqJ01gMEFkjYrV/Nf7s+AS1XxDy7PXOrAAAAAElFTkSuQmCC',img=new Image(),imgFull=new Image(),pgImg=new Image(),imgFullLoaded=!1,pgLoaded=!1;img.src=tinyPng;imgFull.onload=function(){imgFullLoaded=!0};imgFull.src='/purple-pals.svg';pgImg.onload=function(){pgLoaded=!0};pgImg.src='/assets/pg-13.png'; 993 + var uH=null,hST=0,run=true,f=0,netAct=0,shCan=document.createElement('canvas'),shCtx=shCan.getContext('2d'),shF=0; 994 994 var sessionConnected=false,connFlash=0,connFlashStart=0; 995 995 var KWS=['import','export','const','let','function','async','await','return','from','if','else','for','while','class','new','this','var','try','catch']; 996 996 var srcF=[[['// aesthetic.computer','cmt']],[['import','kw'],[' { boot } ','vr'],['from','kw'],[' ','op'],['"./bios"','str']],[['import','kw'],[' { parse } ','vr'],['from','kw'],[' ','op'],['"./parse"','str']],[['','op']],[['async','kw'],[' ','op'],['function','kw'],[' load','fn'],['() {','op']],[[' ','op'],['const','kw'],[' disk ','vr'],['= ','op'],['await','kw'],[' fetch','fn'],['();','op']],[[' ','op'],['return','kw'],[' disk','vr'],[';','op']],[['} ','op']],[['','op']],[['function','kw'],[' paint','fn'],['({ wipe, ink, box }) {','op']],[[' ','op'],['wipe','fn'],['(','op'],['32','num'],[', ','op'],['0','num'],[', ','op'],['64','num'],[');','op']],[[' ','op'],['ink','fn'],['(','op'],['255','num'],[', ','op'],['200','num'],[', ','op'],['100','num'],[');','op']],[[' ','op'],['box','fn'],['(x, y, w, h);','op']],[['} ','op']],[['','op']],[['function','kw'],[' act','fn'],['({ event: e }) {','op']],[[' ','op'],['if','kw'],[' (e.','vr'],['is','fn'],['(','op'],['"touch"','str'],[')) {','op']],[[' ','op'],['console','vr'],['.','op'],['log','fn'],['(e.x, e.y);','op']],[[' }','op']],[['} ','op']],[['','op']],[['class','kw'],[' Aesthetic','tp'],[' {','op']],[[' ','op'],['constructor','fn'],['(cfg) {','op']],[[' ','op'],['this','kw'],['.cfg ','vr'],['= cfg;','op']],[[' ','op'],['this','kw'],['.ready ','vr'],['= ','op'],['false','kw'],[';','op']],[[' }','op']],[[' ','op'],['async','kw'],[' init','fn'],['() {','op']],[[' ','op'],['await','kw'],[' ','op'],['this','kw'],['.','op'],['loadAssets','fn'],['();','op']],[[' ','op'],['this','kw'],['.ready ','vr'],['= ','op'],['true','kw'],[';','op']],[[' }','op']],[['} ','op']],[['','op']],[['export','kw'],[' { boot, paint, act };','vr']]]; ··· 1055 1055 for(var li=0;li<p.toks.length;li++){var ly=9*S+li*3*S;if(ly>p.h-2*S)break; 1056 1056 x.fillStyle=isLightMode?'#8b7355':'#3a5a7a';x.fillText(String(li+1).padStart(3,' '),S,ly); 1057 1057 var tks=p.toks[li],xOff=7*S;for(var j=0;j<tks.length;j++){var tx=tks[j][0];if(xOff+tx.length*1.4*S>p.w-S)break;var rgb=SYN[tks[j][1]]||SYN.op;x.fillStyle='rgb('+rgb[0]+','+rgb[1]+','+rgb[2]+')';x.fillText(tx,xOff,ly);xOff+=x.measureText(tx).width;}}x.restore();}} 1058 + if(shCtx&&(shF++%2===0)){try{var sW=Math.max(1,Math.floor(W*0.5)),sH=Math.max(1,Math.floor(H*0.5));if(shCan.width!==sW)shCan.width=sW;if(shCan.height!==sH)shCan.height=sH;shCtx.clearRect(0,0,sW,sH);shCtx.filter='contrast(1.15) saturate(1.05)';shCtx.drawImage(c,0,0,sW,sH);shCtx.filter='none';x.save();x.globalAlpha=0.25;x.globalCompositeOperation='overlay';x.imageSmoothingEnabled=true;x.drawImage(shCan,0,0,W,H);x.restore();}catch(e){}} 1058 1059 x.globalAlpha=isLightMode?0.02+chaos*0.04:0.04+chaos*0.08;x.fillStyle=isLightMode?'#fff':'#000';for(var yy=0;yy<H;yy+=3*S)x.fillRect(0,yy,W,S); 1059 1060 if(Math.random()<0.15+chaos*0.5){var sc=2+(chaos*6)|0;for(var gi=0;gi<sc;gi++){var gy=Math.random()*H|0,gh=(S+Math.random()*3*S)|0,gs=((Math.random()-0.5)*(5+chaos*12)*S)|0;if(gy+gh<H&&gy>0)try{var sl=x.getImageData(0,gy,W,gh);x.putImageData(sl,gs,gy)}catch(e){}}} 1060 1061 x.globalAlpha=isLightMode?0.03+chaos*0.06:0.05+chaos*0.1;for(var ni=0;ni<(15+chaos*35|0);ni++){x.fillStyle=isKidlisp?(isLightMode?'rgb('+(Math.random()*120|0)+','+(Math.random()*80|0)+','+(Math.random()*30|0)+')':'rgb('+(Math.random()*180|0)+','+(Math.random()*120|0)+','+(Math.random()*50|0)+')'):(isLightMode?'rgb('+(Math.random()*80|0)+','+(Math.random()*40|0)+','+(Math.random()*120|0)+')':'rgb('+(Math.random()*100|0)+','+(Math.random()*50|0)+','+(Math.random()*180|0)+')');x.fillRect(Math.random()*W|0,Math.random()*H|0,S,S);} ··· 1085 1086 var mX=(W-totalW*breathScale)/2+(Math.random()-0.5)*shk*1.5;var mY=startY+li*lineH;for(var ri=0;ri<lineChars;ri++){var ch=line[ri],ci=Math.floor((ri+f*0.2+li*2)%rainbowCols.length),charAge=mAge-(shownSoFar-line.length+ri)/12,entryScale=Math.min(1,charAge*4),charPulse=1+Math.sin((ri+f*0.08+li)*0.5)*0.15,charScale=entryScale*charPulse*breathScale,charKern=(1+Math.sin((ri+f*0.05)*0.7)*kernWave)*3*S;x.save();var charY=mY+Math.sin((ri+f+li)*0.25)*4*S-(1-entryScale)*20*S;x.translate(mX,charY);x.scale(charScale,charScale);x.font='bold '+baseFS+'px YWFTProcessing-Bold, monospace';var cw=x.measureText(ch).width;x.globalAlpha=0.35*entryScale;x.fillStyle=rainbowCols[(ci+2)%rainbowCols.length];x.fillText(ch,-2*S/charScale,0);x.fillStyle=rainbowCols[(ci+4)%rainbowCols.length];x.fillText(ch,S/charScale,-S/charScale);x.globalAlpha=(0.85+Math.sin(f*0.1+ri+li)*0.15)*entryScale;x.fillStyle=rainbowCols[ci];x.fillText(ch,0,0);x.restore();mX+=cw*charScale+charKern;}} 1086 1087 if(motdHandle){var label='mood of the day from '+motdHandle;var labelFS=Math.max(3*S,baseFS*0.45);x.font='bold '+labelFS+'px monospace';var labelW=x.measureText(label).width;var labelX=(W-labelW)/2;var labelY=Math.min(H-3*S,startY+linesMotd.length*lineH+4*S);x.globalAlpha=0.6+Math.sin(f*0.05)*0.15;x.fillStyle=isLightMode?'rgb(120,80,140)':'rgb(220,180,240)';x.fillText(label,labelX,labelY);x.globalAlpha=1;} 1087 1088 x.restore();} 1089 + if(!isKidlisp&&pgLoaded){if(pgStart===null&&(imgFullLoaded||performance.now()-bootStart>900))pgStart=performance.now();var pgAge=pgStart?(performance.now()-pgStart)/1000:0,pgReady=pgStart&&pgAge>0.35,pgFlick=pgReady&&(Math.sin(t*18)>-0.2);if(pgFlick){var m=3*S,bSafe=8*S,aspect=pgImg.naturalWidth&&pgImg.naturalHeight?pgImg.naturalWidth/pgImg.naturalHeight:2,bW=Math.min(44*S,Math.max(28*S,W*0.185)),bH=bW/aspect,slide=(Math.sin(t*1.2)*2+Math.sin(t*2.4)*1)*S,bX=W-bW-m+sx*0.4*S+4*S+slide,bY=H-bH-m-bSafe+sy*0.4*S,jit=0.6+chaos*1.8,rev=Math.min(1,Math.max(0,(pgAge-0.35)/0.8));x.globalCompositeOperation='screen';x.globalAlpha=0.18*rev;x.filter='hue-rotate(18deg) saturate(1.4)';x.imageSmoothingEnabled=true;x.drawImage(pgImg,bX-jit*1.2*S,bY+jit*0.4*S,bW,bH);x.filter='hue-rotate(-18deg) saturate(1.4)';x.drawImage(pgImg,bX+jit*1.1*S,bY-jit*0.5*S,bW,bH);x.filter='none';x.globalCompositeOperation='multiply';x.globalAlpha=0.12*rev;x.drawImage(pgImg,bX+jit*0.5*S,bY+jit*0.2*S,bW,bH);x.globalCompositeOperation='screen';x.globalAlpha=(0.25+Math.sin(t*10)*0.08)*rev;x.filter='blur(6px)';x.drawImage(pgImg,bX,bY,bW,bH);x.filter='none';x.globalCompositeOperation='source-over';x.globalAlpha=0.88*rev;x.drawImage(pgImg,bX,bY,bW,bH);x.imageSmoothingEnabled=false;x.globalCompositeOperation='multiply';x.globalAlpha=(0.4+Math.sin(t*8)*0.12)*rev;x.fillStyle='rgb(255, 220, 80)';x.fillRect(bX,bY,bW,bH);x.globalCompositeOperation='multiply';x.globalAlpha=(0.18+chaos*0.15)*rev;x.fillStyle='#000';for(var sy2=bY;sy2<bY+bH;sy2+=2){x.fillRect(bX,sy2,bW,1);}x.globalAlpha=1;}} 1088 1090 // Connection status color tint overlay - red/magenta before connected, green/cyan flash on connect 1089 1091 if(!sessionConnected){x.globalCompositeOperation='multiply';x.globalAlpha=0.15+Math.sin(t*3)*0.05;x.fillStyle='rgb(255,80,120)';x.fillRect(0,0,W,H);x.globalCompositeOperation='source-over';} 1090 1092 if(connFlash>0.01){x.globalCompositeOperation='screen';x.globalAlpha=connFlash*0.6;x.fillStyle='rgb(80,255,180)';x.fillRect(0,0,W,H);x.globalCompositeOperation='source-over';}
+21 -49
system/public/aesthetic.computer/disks/notepat.mjs
··· 934 934 rateLabel = null, 935 935 rateText = null, 936 936 ) { 937 - const textWidth = MIDI_BADGE_TEXT.length * glyphMetrics.width; 938 - const rateLabelWidth = rateLabel ? rateLabel.length * glyphMetrics.width : 0; 939 - const rateWidth = rateText ? rateText.length * glyphMetrics.width : 0; 940 - const rateLineWidth = 941 - rateLabelWidth > 0 942 - ? rateLabelWidth + MIDI_RATE_LABEL_GAP + rateWidth 943 - : rateWidth; 937 + const combinedText = rateText 938 + ? `${MIDI_BADGE_TEXT} ${MIDI_RATE_LABEL_TEXT} ${rateText}` 939 + : MIDI_BADGE_TEXT; 940 + const combinedWidth = combinedText.length * glyphMetrics.width; 944 941 const width = 945 - Math.max(textWidth, rateLineWidth) + MIDI_BADGE_PADDING_X + MIDI_BADGE_PADDING_RIGHT; 946 - const lineGap = rateText ? 2 : 0; 947 - const height = 948 - glyphMetrics.height * (rateText ? 2 : 1) + 949 - MIDI_BADGE_PADDING_Y * 2 + 950 - lineGap; 942 + combinedWidth + MIDI_BADGE_PADDING_X + MIDI_BADGE_PADDING_RIGHT; 943 + const height = glyphMetrics.height + MIDI_BADGE_PADDING_Y * 2; 951 944 952 945 // In compact mode, center the badge at bottom 953 946 const x = compactMode ··· 1302 1295 wipe(bg); 1303 1296 } 1304 1297 1298 + const isSplitMode = screen.height < 200; 1299 + const modeTypeface = isSplitMode ? "MatrixChunky8" : undefined; 1300 + 1305 1301 // 🎛️ Mode toggle buttons (quick, room, slide) 1306 1302 if (quickBtn && !paintPictureOverlay && !projector) { 1307 1303 quickBtn.paint((btn) => { ··· 1312 1308 } 1313 1309 box(btn.box.x, btn.box.y, btn.box.w, btn.box.h); 1314 1310 ink(quickFade ? (btn.down ? "lime" : "green") : (btn.down ? "gray" : "darkgray")); 1315 - write("quick", btn.box.x + 3, btn.box.y + 2); 1311 + write("quick", btn.box.x + 3, btn.box.y + 2, undefined, undefined, false, modeTypeface); 1316 1312 }); 1317 1313 } 1318 1314 ··· 1325 1321 } 1326 1322 box(btn.box.x, btn.box.y, btn.box.w, btn.box.h); 1327 1323 ink(roomMode ? (btn.down ? "cyan" : "blue") : (btn.down ? "gray" : "darkgray")); 1328 - write("room", btn.box.x + 3, btn.box.y + 2); 1324 + write("room", btn.box.x + 3, btn.box.y + 2, undefined, undefined, false, modeTypeface); 1329 1325 }); 1330 1326 } 1331 1327 ··· 1338 1334 } 1339 1335 box(btn.box.x, btn.box.y, btn.box.w, btn.box.h); 1340 1336 ink(slide ? (btn.down ? "orange" : "red") : (btn.down ? "gray" : "darkgray")); 1341 - write("slide", btn.box.x + 3, btn.box.y + 2); 1337 + write("slide", btn.box.x + 3, btn.box.y + 2, undefined, undefined, false, modeTypeface); 1342 1338 }); 1343 1339 } 1344 1340 ··· 1616 1612 const { x, y, width, height } = metrics; 1617 1613 const bgColor = connected ? connectedBackground : disconnectedBackground; 1618 1614 const textColor = connected ? connectedText : disconnectedText; 1619 - const lineGap = rateText ? 2 : 0; 1615 + const combinedText = rateText 1616 + ? `${MIDI_BADGE_TEXT} ${MIDI_RATE_LABEL_TEXT} ${rateText}` 1617 + : MIDI_BADGE_TEXT; 1620 1618 1621 1619 ink(...bgColor).box(x, y, width, height); 1622 1620 1623 1621 ink(...textColor).write( 1624 - MIDI_BADGE_TEXT, 1622 + combinedText, 1625 1623 { x: x + MIDI_BADGE_PADDING_X, y: y + MIDI_BADGE_PADDING_Y }, 1626 1624 undefined, 1627 1625 undefined, 1628 1626 false, 1629 1627 "MatrixChunky8", 1630 1628 ); 1631 - 1632 - if (rateText) { 1633 - const rateWidth = rateText.length * matrixGlyphMetrics.width; 1634 - const rateLabelWidth = rateLabel ? rateLabel.length * matrixGlyphMetrics.width : 0; 1635 - const rateY = 1636 - y + MIDI_BADGE_PADDING_Y + matrixGlyphMetrics.height + lineGap; 1637 - 1638 - if (rateLabel) { 1639 - ink(...rateTextColor).write( 1640 - rateLabel, 1641 - { x: x + MIDI_BADGE_PADDING_X, y: rateY }, 1642 - undefined, 1643 - undefined, 1644 - false, 1645 - "MatrixChunky8", 1646 - ); 1647 - } 1648 - 1649 - ink(...rateTextColor).write( 1650 - rateText, 1651 - { 1652 - x: x + width - MIDI_BADGE_PADDING_RIGHT - rateWidth, 1653 - y: rateY, 1654 - }, 1655 - undefined, 1656 - undefined, 1657 - false, 1658 - "MatrixChunky8", 1659 - ); 1660 - } 1661 1629 }; 1662 1630 1663 1631 ··· 3714 3682 buttonNote = note.toLowerCase(); 3715 3683 } 3716 3684 3685 + // Store the exact note mapped for this key to ensure reliable key-up cleanup 3686 + downs[key] = buttonNote; 3687 + 3717 3688 // Adjust active octave based on extension note 3718 3689 // if needed. 3719 3690 ··· 3803 3774 } 3804 3775 3805 3776 if (downs[key]) { 3777 + const heldNote = typeof downs[key] === "string" ? downs[key] : null; 3806 3778 delete downs[key]; 3807 3779 3808 3780 // let buttonNote = key; ··· 3907 3879 return buttonNote; 3908 3880 } 3909 3881 3910 - const buttonNote = noteFromKey(key); 3882 + const buttonNote = heldNote ?? noteFromKey(key); 3911 3883 3912 3884 const orderedTones = orderedByCount(tonestack); 3913 3885
+85 -13
system/public/kidlisp.com/index.html
··· 2065 2065 flex-direction: column; 2066 2066 align-items: center; 2067 2067 justify-content: center; 2068 - gap: 10px; 2068 + gap: 12px; 2069 2069 background: #1a1a1a; 2070 2070 box-sizing: border-box; 2071 2071 padding: 16px; 2072 2072 z-index: 10; 2073 + overflow: auto; 2073 2074 } 2074 2075 .ff1-pairing.open { display: flex; } 2076 + .ff1-pairing-close { 2077 + position: absolute; 2078 + top: 8px; 2079 + right: 8px; 2080 + background: rgba(255,255,255,0.15); 2081 + border: none; 2082 + border-radius: 4px; 2083 + width: 28px; 2084 + height: 28px; 2085 + color: white; 2086 + font-size: 16px; 2087 + cursor: pointer; 2088 + display: flex; 2089 + align-items: center; 2090 + justify-content: center; 2091 + transition: background 0.2s; 2092 + } 2093 + .ff1-pairing-close:hover { background: rgba(255,255,255,0.25); } 2094 + .ff1-pairing-content { 2095 + display: flex; 2096 + flex-direction: column; 2097 + align-items: center; 2098 + gap: 12px; 2099 + max-width: 100%; 2100 + } 2075 2101 .ff1-pairing-hint { 2076 - font-size: 11px; 2077 - opacity: 0.7; 2078 - text-align: center; 2079 - line-height: 1.4; 2080 - max-width: 200px; 2102 + font-size: 12px; 2103 + opacity: 0.85; 2104 + text-align: left; 2105 + line-height: 1.6; 2081 2106 color: white; 2082 2107 } 2083 2108 .ff1-pairing-qr { 2084 - width: 140px; 2085 - height: 140px; 2109 + width: 120px; 2110 + height: 120px; 2086 2111 min-width: 80px; 2087 2112 min-height: 80px; 2088 2113 background: #fff; ··· 2354 2379 align-items: center; 2355 2380 gap: 20px; 2356 2381 } 2357 - #boot-screen-ff1 .ff1-pairing { 2382 + #boot-screen-ff1 .ff1-pairing-qr { 2383 + width: 140px; 2384 + height: 140px; 2385 + } 2386 + } 2387 + 2388 + /* FF1 pairing overlay - horizontal layout for wider panels */ 2389 + @container preview (min-width: 420px) { 2390 + .ff1-pairing-content { 2358 2391 flex-direction: row; 2359 2392 align-items: center; 2360 - gap: 16px; 2393 + gap: 24px; 2361 2394 } 2362 - #boot-screen-ff1 .ff1-pairing-qr { 2395 + .ff1-pairing-hint { 2396 + text-align: left; 2397 + } 2398 + .ff1-pairing-qr { 2363 2399 width: 140px; 2364 2400 height: 140px; 2401 + } 2402 + } 2403 + 2404 + /* FF1 pairing overlay - larger QR for spacious panels */ 2405 + @container preview (min-width: 500px) and (min-height: 300px) { 2406 + .ff1-pairing-qr { 2407 + width: 160px; 2408 + height: 160px; 2409 + } 2410 + .ff1-pairing-hint { 2411 + font-size: 13px; 2412 + } 2413 + } 2414 + 2415 + /* FF1 pairing overlay - compact vertical for narrow */ 2416 + @container preview (max-width: 300px) { 2417 + .ff1-pairing { 2418 + padding: 12px; 2419 + gap: 8px; 2420 + } 2421 + .ff1-pairing-qr { 2422 + width: 90px; 2423 + height: 90px; 2424 + } 2425 + .ff1-pairing-hint { 2426 + font-size: 10px; 2427 + } 2428 + .ff1-pairing-close { 2429 + width: 24px; 2430 + height: 24px; 2431 + font-size: 14px; 2365 2432 } 2366 2433 } 2367 2434 ··· 8101 8168 <button id="ff1-pair-btn" class="ff1-btn">Pair</button> 8102 8169 </div> 8103 8170 <div class="ff1-pairing" id="ff1-pairing"> 8104 - <canvas id="ff1-qr-canvas" class="ff1-pairing-qr"></canvas> 8105 - <div class="ff1-pairing-hint">1. Show FF1 QR on your TV in the Feral File App<br>2. Scan this QR with your iPhone<br>3. Scan your TV</div> 8171 + <button class="ff1-pairing-close" id="ff1-pairing-close" title="Close">✕</button> 8172 + <div class="ff1-pairing-content"> 8173 + <canvas id="ff1-qr-canvas" class="ff1-pairing-qr"></canvas> 8174 + <div class="ff1-pairing-hint">1. Show FF1 QR on your TV in the Feral File App<br>2. Scan this QR with your iPhone<br>3. Scan your TV</div> 8175 + </div> 8106 8176 <div class="ff1-pairing-status" id="ff1-pairing-status">Waiting…</div> 8107 8177 <div class="ff1-pairing-link" id="ff1-pairing-link" title="Click to copy"></div> 8108 8178 </div> ··· 9081 9151 9082 9152 const testBtn = document.getElementById('ff1-test-btn'); 9083 9153 const pairBtn = document.getElementById('ff1-pair-btn'); 9154 + const closeBtn = document.getElementById('ff1-pairing-close'); 9084 9155 const linkEl = document.getElementById('ff1-pairing-link'); 9085 9156 const statusEl = document.getElementById('ff1-status'); 9086 9157 ··· 9094 9165 9095 9166 if (testBtn) testBtn.addEventListener('click', testFF1Connection); 9096 9167 if (pairBtn) pairBtn.addEventListener('click', toggleFF1Pairing); 9168 + if (closeBtn) closeBtn.addEventListener('click', toggleFF1Pairing); 9097 9169 if (linkEl) linkEl.addEventListener('click', async () => { 9098 9170 const text = linkEl.textContent || ''; 9099 9171 if (!text) return;
+109
system/templates/index.html
··· 161 161 const tinyPng = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsTAAALEwEAmpwYAAABWUlEQVR4nN2Ru0pDQRCGg3fUzk5bEVS0ELxVwQsYTRBvJ5ed3VgZVBB8g1Q+gCDIzq6IluclUktmT1Kk8mWUWT2RTcBep1nmZ//vn53NZP5XOWU3ndTlOIoHe/UE8MxJM8s9SdxuVR9nAnMneph0Up+25esECSwEAGFK3gh4SwIvnDQ5UrgXAN5v7kcJ9HUjWx/iKZwyJ6RsxMkEWPzSTY7A5psVsxaYG+fPYyTNFZ8+qaaHSRjhzcpGrSou+knKdprH70t2Ci+Z7qS56wIAj9M7DPkJskcBIAG96gHKzKdv/X5vkU+/E7B5AtxI4GnBKasSpXf5brBIqulxB/qQjZzO4/uxwe68qZep7l6EXmKNQemv+GIzE5vKHKTL87owpbbCOQK9wj1Jvc59prcSwAqnd6J4hM0pgGGsf9TrA32mACD0Pu/Dp1bNcqJ01gMEFkjYrV/Nf7s+AS1XxDy7PXOrAAAAAElFTkSuQmCC'; 162 162 const img = new Image(); 163 163 const imgFull = new Image(); 164 + const pg13Img = new Image(); 164 165 let imgFullLoaded = false; 166 + let pg13Loaded = false; 165 167 img.src = tinyPng; // Instant load 166 168 imgFull.onload = () => { imgFullLoaded = true; }; 167 169 imgFull.src = '/purple-pals.svg'; 170 + pg13Img.onload = () => { pg13Loaded = true; }; 171 + pg13Img.src = '/assets/pg-13.png'; 168 172 169 173 // Boot state 170 174 let lines = []; ··· 175 179 let lastLogTime = performance.now(); 176 180 let logBurst = 0; 177 181 const bootStartTime = performance.now(); 182 + let pg13RevealStart = null; 178 183 179 184 // User handle display 180 185 let userHandle = null; ··· 396 401 const setSessionConnected = (connected) => { 397 402 sessionConnected = connected; 398 403 sessionConnectTime = performance.now(); 404 + }; 405 + 406 + const sharpenCanvas = document.createElement('canvas'); 407 + const sharpenCtx = sharpenCanvas.getContext('2d'); 408 + let sharpenFrame = 0; 409 + 410 + const applySoftSharpen = () => { 411 + if (!sharpenCtx || !W || !H) return; 412 + if ((sharpenFrame++ % 2) !== 0) return; 413 + const scale = 0.5; 414 + const sw = Math.max(1, Math.floor(W * scale)); 415 + const sh = Math.max(1, Math.floor(H * scale)); 416 + if (sharpenCanvas.width !== sw) sharpenCanvas.width = sw; 417 + if (sharpenCanvas.height !== sh) sharpenCanvas.height = sh; 418 + try { 419 + sharpenCtx.clearRect(0, 0, sw, sh); 420 + sharpenCtx.filter = 'contrast(1.15) saturate(1.05)'; 421 + sharpenCtx.drawImage(canvas, 0, 0, sw, sh); 422 + sharpenCtx.filter = 'none'; 423 + 424 + ctx.save(); 425 + ctx.globalAlpha = 0.25; 426 + ctx.globalCompositeOperation = 'overlay'; 427 + ctx.imageSmoothingEnabled = true; 428 + ctx.drawImage(sharpenCanvas, 0, 0, W, H); 429 + ctx.restore(); 430 + } catch { 431 + // ignore sharpen failures 432 + } 399 433 }; 400 434 401 435 const animate = () => { ··· 690 724 }); 691 725 ctx.globalAlpha = 1; 692 726 727 + // PG-13 badge (bottom-right, above bottom bars, VHS burn-in) 728 + if (!isKidlispMode && pg13Loaded) { 729 + if (pg13RevealStart === null && (imgFullLoaded || performance.now() - bootStartTime > 900)) { 730 + pg13RevealStart = performance.now(); 731 + } 732 + const pgAge = pg13RevealStart ? (performance.now() - pg13RevealStart) / 1000 : 0; 733 + const pgReady = pg13RevealStart && pgAge > 0.35; 734 + const pgFlicker = pgReady && (Math.sin(t * 18) > -0.2); 735 + if (!pgFlicker) { 736 + // skip drawing until flicker settles 737 + } else { 738 + const margin = 8; 739 + const bottomSafe = 26; 740 + const aspect = (pg13Img.naturalWidth && pg13Img.naturalHeight) 741 + ? (pg13Img.naturalWidth / pg13Img.naturalHeight) 742 + : 2; 743 + const maxW = Math.min(160, Math.max(96, W * 0.185)); 744 + const badgeW = maxW; 745 + const badgeH = badgeW / aspect; 746 + const slide = Math.sin(t * 1.2) * 2 + Math.sin(t * 2.4) * 1; 747 + const badgeX = W - badgeW - margin + shakeX * 0.4 + 10 + slide; 748 + const badgeY = H - badgeH - margin - bottomSafe + shakeY * 0.4; 749 + const jitter = 0.6 + chaos * 1.8; 750 + 751 + const revealAlpha = Math.min(1, Math.max(0, (pgAge - 0.35) / 0.8)); 752 + 753 + // Color separation pass 754 + ctx.globalCompositeOperation = 'screen'; 755 + ctx.globalAlpha = 0.18 * revealAlpha; 756 + ctx.filter = 'hue-rotate(18deg) saturate(1.4)'; 757 + ctx.imageSmoothingEnabled = true; 758 + ctx.drawImage(pg13Img, badgeX - jitter * 1.2, badgeY + jitter * 0.4, badgeW, badgeH); 759 + ctx.filter = 'hue-rotate(-18deg) saturate(1.4)'; 760 + ctx.drawImage(pg13Img, badgeX + jitter * 1.1, badgeY - jitter * 0.5, badgeW, badgeH); 761 + ctx.filter = 'none'; 762 + 763 + // Burn-in shadow 764 + ctx.globalCompositeOperation = 'multiply'; 765 + ctx.globalAlpha = 0.12 * revealAlpha; 766 + ctx.drawImage(pg13Img, badgeX + jitter * 0.5, badgeY + jitter * 0.2, badgeW, badgeH); 767 + 768 + // Neon yellow glow (pre-pass) 769 + ctx.globalCompositeOperation = 'screen'; 770 + ctx.globalAlpha = (0.25 + Math.sin(t * 10) * 0.08) * revealAlpha; 771 + ctx.filter = 'blur(6px)'; 772 + ctx.drawImage(pg13Img, badgeX, badgeY, badgeW, badgeH); 773 + ctx.filter = 'none'; 774 + 775 + // Main pass 776 + ctx.globalCompositeOperation = 'source-over'; 777 + ctx.globalAlpha = 0.88 * revealAlpha; 778 + ctx.drawImage(pg13Img, badgeX, badgeY, badgeW, badgeH); 779 + ctx.imageSmoothingEnabled = false; 780 + 781 + // Yellow tint on whites (flicker) 782 + ctx.globalCompositeOperation = 'multiply'; 783 + ctx.globalAlpha = (0.4 + Math.sin(t * 8) * 0.12) * revealAlpha; 784 + ctx.fillStyle = 'rgb(255, 220, 80)'; 785 + ctx.fillRect(badgeX, badgeY, badgeW, badgeH); 786 + 787 + // Extra scanlines over badge 788 + ctx.globalCompositeOperation = 'multiply'; 789 + ctx.globalAlpha = (0.18 + chaos * 0.15) * revealAlpha; 790 + ctx.fillStyle = '#000'; 791 + for (let y = badgeY; y < badgeY + badgeH; y += 2) { 792 + ctx.fillRect(badgeX, y, badgeW, 1); 793 + } 794 + 795 + ctx.globalAlpha = 1; 796 + } 797 + } 798 + 799 + // Soft global sharpen 800 + applySoftSharpen(); 801 + 693 802 // Subtle scanlines 694 803 ctx.globalAlpha = 0.03 + chaos * 0.05; 695 804 ctx.fillStyle = '#000';