Monorepo for Aesthetic.Computer aesthetic.computer

fix: dj.mjs — no more text overlap, add tappable buttons

Bottom panel reorganized: progress bar, time/speed, then button row
with proper spacing. Buttons: Prev, Play/Pause, Next, 1x, Scan —
all tappable in addition to existing keyboard shortcuts.
USB indicator moved to top-right under track counter.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+71 -17
+71 -17
fedac/native/pieces/dj.mjs
··· 22 22 let scratchSpeed = 0; // current scratch velocity (-2 to 2) 23 23 let wasPlaying = false; // was playing before scratch started 24 24 25 + // Button layout (computed in paint, used in act) 26 + let buttons = []; // [{x, y, w, h, id, label}] 27 + 25 28 function isAudio(name) { 26 29 if (name.startsWith(".")) return false; // skip metadata (._xxx.m4a etc) 27 30 const dot = name.lastIndexOf("."); ··· 89 92 const w = screen?.width || 320; 90 93 const h = screen?.height || 240; 91 94 95 + // --- Button tap check (before scratch) --- 96 + if (e.is("touch")) { 97 + for (const b of buttons) { 98 + if (e.x >= b.x && e.x <= b.x + b.w && e.y >= b.y && e.y <= b.y + b.h) { 99 + if (b.id === "play") { 100 + if (d?.loaded) { 101 + if (d.playing) { dk.pause(0); spinSpeed = 0; msg("paused"); } 102 + else { dk.play(0); spinSpeed = 1; msg("playing"); } 103 + } 104 + } else if (b.id === "next") { trackIdx++; loadTrack(sound, tts); } 105 + else if (b.id === "prev") { trackIdx = Math.max(0, trackIdx - 1); loadTrack(sound, tts); } 106 + else if (b.id === "reset") { if (d?.loaded) { dk.setSpeed(0, 1); msg("1.00x"); } } 107 + else if (b.id === "scan") { 108 + mounted = system?.mountMusic?.() || false; 109 + scan(system, tts); 110 + if (files.length > 0) { trackIdx = 0; loadTrack(sound, tts); } 111 + } 112 + return; 113 + } 114 + } 115 + } 116 + 92 117 // --- Mouse/touchpad scratch (radial around platter center) --- 93 118 if (e.is("touch")) { 94 119 dragging = true; 95 120 dragLastX = e.x; 96 121 dragLastY = e.y; 97 122 wasPlaying = d?.playing || false; 98 - // Keep playing but we'll modulate speed 99 123 scratchSpeed = 0; 100 124 return; 101 125 } ··· 266 290 ink(fg, fg, fg + 10); 267 291 write(title.slice(0, maxC), { x: 4, y: 3, size: 1, font: F }); 268 292 293 + // Track counter + USB (top right) 294 + if (files.length > 0) { 295 + ink(dim, dim, dim + 10); 296 + const tc = `${trackIdx + 1}/${files.length}`; 297 + write(tc, { x: w - tc.length * CW - 4, y: 3, size: 1, font: F }); 298 + } 299 + ink(usbConnected ? 60 : dim, usbConnected ? 180 : dim, usbConnected ? 60 : dim); 300 + write(usbConnected ? "USB" : "---", { x: w - 22, y: 14, size: 1, font: F }); 301 + 302 + // --- Bottom panel (no overlaps) --- 303 + // Layout from bottom up: 304 + // h-12: buttons row 305 + // h-24: time / speed 306 + // h-32: progress bar 307 + 269 308 // Progress bar 270 - const barY = h - 22; 309 + const barY = h - 34; 271 310 const barW = w - 8; 272 311 const progress = d.duration > 0 ? d.position / d.duration : 0; 273 312 ink(T.bg[0] + 15, T.bg[1] + 15, T.bg[2] + 20); ··· 283 322 write(spd, { x: w - spd.length * CW - 4, y: barY + 7, size: 1, font: F }); 284 323 } 285 324 286 - // Track counter 287 - if (files.length > 0) { 288 - ink(dim, dim, dim + 10); 289 - const tc = `${trackIdx + 1}/${files.length}`; 290 - write(tc, { x: w - tc.length * CW - 4, y: 3, size: 1, font: F }); 325 + // --- Button row --- 326 + buttons = []; 327 + const btnY = h - 14; 328 + const btnH = 12; 329 + const btnDefs = [ 330 + { id: "prev", label: "< Prev" }, 331 + { id: "play", label: d.playing ? "Pause" : "Play" }, 332 + { id: "next", label: "Next >" }, 333 + { id: "reset", label: "1x" }, 334 + { id: "scan", label: "Scan" }, 335 + ]; 336 + const gap = 3; 337 + const totalGap = gap * (btnDefs.length - 1); 338 + const btnW = Math.floor((w - 8 - totalGap) / btnDefs.length); 339 + for (let i = 0; i < btnDefs.length; i++) { 340 + const bx = 4 + i * (btnW + gap); 341 + const bd = btnDefs[i]; 342 + buttons.push({ x: bx, y: btnY, w: btnW, h: btnH, id: bd.id, label: bd.label }); 343 + // Button background 344 + ink(T.bg[0] + 18, T.bg[1] + 18, T.bg[2] + 24); 345 + box(bx, btnY, btnW, btnH); 346 + // Button border 347 + ink(dim + 10, dim + 10, dim + 20); 348 + box(bx, btnY, btnW, btnH, "outline"); 349 + // Button label (centered) 350 + const lx = bx + Math.floor((btnW - bd.label.length * CW) / 2); 351 + const ly = btnY + 2; 352 + ink(fg, fg, fg + 10); 353 + write(bd.label, { x: lx, y: ly, size: 1, font: F }); 291 354 } 292 355 293 - // Status bar 294 - const sY = h - 10; 295 - ink(dim, dim, dim + 10); 296 - write("Spc:play N/P:trk </>:spd Z:1x R:scan", { x: 4, y: sY, size: 1, font: F }); 297 - 298 356 // Drag state 299 357 if (dragging) { 300 358 ink(255, 100, 60); ··· 305 363 if (message && frame - messageFrame < 120) { 306 364 const fade = Math.max(0, 255 - Math.floor((frame - messageFrame) * 2.5)); 307 365 ink(255, 220, 60, fade); 308 - write(message.slice(0, maxC), { x: 4, y: 14, size: 1, font: F }); 366 + write(message.slice(0, maxC), { x: 4, y: 16, size: 1, font: F }); 309 367 } 310 - 311 - // USB indicator 312 - ink(usbConnected ? 60 : dim, usbConnected ? 180 : dim, usbConnected ? 60 : dim); 313 - write(usbConnected ? "USB" : "---", { x: w - 22, y: sY, size: 1, font: F }); 314 368 } 315 369 316 370 function sim({ system, tts, sound }) {