Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

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

prompt + blank: replace spinning cube with hinged laptop wireframe, use TextButton

Swap the wireframe cube on the login curtain and blank sales page
for a laptop/pizza-box shape — two thin halves that open and close
on a hinge. Blank page buttons now use ui.TextButton for proper
auto-sizing instead of manual pixel math.

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

+223 -162
+144 -113
system/public/aesthetic.computer/disks/blank.mjs
··· 1 1 // blank, 26.03.20 2 2 // AC Blank — AC Native Laptop product page & checkout 3 3 4 - const { floor, sin, min, max } = Math; 4 + const { floor, sin, cos, abs, min, max, PI } = Math; 5 5 6 6 // Pricing (cents) 7 7 const pricing = { ··· 24 24 let buyPending = false; 25 25 let thanks = false; 26 26 27 - // UI elements 27 + // UI elements (TextButtons) 28 28 let buyBtn = null; 29 29 let tierBtns = []; 30 30 let currencyBtn = null; ··· 32 32 // Animation 33 33 let frame = 0; 34 34 35 - const pad = 6; 36 - const charW = 8; 37 35 const charH = 16; 38 36 39 37 function displayAmount(amt, cur) { ··· 67 65 fetchCheckout(api); 68 66 } 69 67 68 + function tierText(i) { 69 + const t = tiers[i]; 70 + const tierAmt = currency === "dkk" ? t.dkk : t.amount; 71 + return `${displayAmount(tierAmt, currency)} ${t.desc}`; 72 + } 73 + 70 74 function setupButtons(ui, screen) { 71 - const w = screen.width; 72 75 const h = screen.height; 73 76 74 77 // Buy button (bottom center) 75 - const buyText = getBuyText(); 76 - const buyW = buyText.length * charW + pad * 4; 77 - const buyH = charH + pad * 3; 78 - buyBtn = new ui.Button( 79 - floor((w - buyW) / 2), 80 - h - buyH - 12, 81 - buyW, 82 - buyH, 83 - ); 78 + buyBtn = new ui.TextButton(getBuyText(), { center: "x", bottom: 12, screen }); 84 79 85 - // Tier buttons (3 across, above buy button) 86 - const tierW = floor(min(w - 24, 280)); 87 - const tierH = charH + pad * 2; 88 - const tierY = buyBtn.box.y - (tierH + 6) * 3 - 12; 80 + // Tier buttons (stacked, above buy button) 81 + const tierStartY = buyBtn.btn.box.y - (buyBtn.height + 6) * 3 - 8; 89 82 tierBtns = tiers.map((t, i) => { 90 - const btn = new ui.Button( 91 - floor((w - tierW) / 2), 92 - tierY + i * (tierH + 6), 93 - tierW, 94 - tierH, 95 - ); 96 - btn._tierIndex = i; 97 - return btn; 83 + return new ui.TextButton(tierText(i), { 84 + center: "x", 85 + y: tierStartY + i * (buyBtn.height + 6), 86 + screen, 87 + }); 98 88 }); 99 89 100 90 // Currency toggle (top right) 101 - const curText = currency.toUpperCase(); 102 - const curW = curText.length * charW + pad * 2; 103 - currencyBtn = new ui.Button(w - curW - 8, 8, curW, charH + pad); 91 + currencyBtn = new ui.TextButton(currency.toUpperCase(), { 92 + top: 8, 93 + right: 8, 94 + screen, 95 + }); 104 96 } 105 97 106 98 async function fetchCheckout(api) { ··· 195 187 screen, 196 188 }); 197 189 190 + // 💻 Wireframe laptop (two hinged halves) 191 + { 192 + const cx = floor(w / 2); 193 + const laptopTop = specY + charH * 2 + 16; 194 + const laptopBottom = tierBtns.length > 0 ? tierBtns[0].btn.box.y - 12 : h * 0.55; 195 + const cy = floor((laptopTop + laptopBottom) / 2); 196 + const size = min(w, laptopBottom - laptopTop) * 0.28; 197 + const fov = 260; 198 + const ay = frame * 0.008; 199 + const ax = frame * 0.005; 200 + 201 + // Hinge angle: smoothly open and close 202 + const hingeAngle = (sin(frame * 0.012) * 0.5 + 0.5) * PI * 0.85 + PI * 0.1; 203 + 204 + // Half-box dimensions: wide, thin, moderate depth 205 + const hw = 1.4, hh = 0.08, hd = 0.9; 206 + 207 + // Bottom half (base) 208 + const base = [ 209 + [-hw, -hh, -hd], [ hw, -hh, -hd], [ hw, hh, -hd], [-hw, hh, -hd], 210 + [-hw, -hh, hd], [ hw, -hh, hd], [ hw, hh, hd], [-hw, hh, hd], 211 + ]; 212 + 213 + // Top half (lid) — hinged at back edge 214 + const lidLocal = [ 215 + [-hw, -hh, 0], [ hw, -hh, 0], [ hw, hh, 0], [-hw, hh, 0], 216 + [-hw, -hh, 2 * hd], [ hw, -hh, 2 * hd], [ hw, hh, 2 * hd], [-hw, hh, 2 * hd], 217 + ]; 218 + const cosH = cos(hingeAngle), sinH = sin(hingeAngle); 219 + const lid = lidLocal.map(([lx, ly, lz]) => { 220 + const ry = ly * cosH - lz * sinH; 221 + const rz = ly * sinH + lz * cosH; 222 + return [lx, ry + hh, rz - hd]; 223 + }); 224 + 225 + const halfEdges = [ 226 + [0,1],[1,2],[2,3],[3,0], 227 + [4,5],[5,6],[6,7],[7,4], 228 + [0,4],[1,5],[2,6],[3,7], 229 + ]; 230 + 231 + const project = ([x, y, z]) => { 232 + let rx = x * cos(ay) - z * sin(ay); 233 + let rz = x * sin(ay) + z * cos(ay); 234 + let ry = y * cos(ax) - rz * sin(ax); 235 + rz = y * sin(ax) + rz * cos(ax); 236 + const scale = fov / (fov + rz * size); 237 + return [cx + rx * size * scale, cy + ry * size * scale, rz]; 238 + }; 239 + 240 + const projBase = base.map(project); 241 + const projLid = lid.map(project); 242 + 243 + const waveOffset = frame * 0.05; 244 + const drawEdges = (proj, edgeOffset) => { 245 + halfEdges.forEach(([a, b], i) => { 246 + const depth = (proj[a][2] + proj[b][2]) / 2; 247 + const brightness = 0.55 + depth * 0.15; 248 + const hue = (((i + edgeOffset) * 0.37 + sin(frame * 0.02 + i + edgeOffset) * 0.3) + waveOffset) % 1; 249 + const sector = abs(hue) * 6; 250 + const f = sector - floor(sector); 251 + let r, g, bl; 252 + const s = floor(sector) % 6; 253 + if (s === 0) { r = 1; g = f; bl = 0; } 254 + else if (s === 1) { r = 1 - f; g = 1; bl = 0; } 255 + else if (s === 2) { r = 0; g = 1; bl = f; } 256 + else if (s === 3) { r = 0; g = 1 - f; bl = 1; } 257 + else if (s === 4) { r = f; g = 0; bl = 1; } 258 + else { r = 1; g = 0; bl = 1 - f; } 259 + ink( 260 + floor(r * 255 * brightness), 261 + floor(g * 255 * brightness), 262 + floor(bl * 255 * brightness), 263 + 140, 264 + ).line( 265 + proj[a][0], proj[a][1], 266 + proj[b][0], proj[b][1], 267 + ); 268 + }); 269 + }; 270 + 271 + drawEdges(projBase, 0); 272 + drawEdges(projLid, 12); 273 + 274 + // Vertex particles 275 + const particleColors = [ 276 + [255, 80, 200], [80, 255, 220], [255, 255, 80], 277 + [80, 200, 255], [255, 120, 80], [180, 80, 255], 278 + ]; 279 + [...projBase, ...projLid].forEach(([px, py], i) => { 280 + const pColor = particleColors[(i + floor(frame * 0.04)) % particleColors.length]; 281 + const flicker = 0.6 + sin(frame * 0.1 + i * 1.3) * 0.4; 282 + ink(...pColor, floor(flicker * 150)).box(px - 1, py - 1, 2, 2); 283 + }); 284 + } 285 + 198 286 // Tier buttons 287 + const $ = { ink }; 199 288 tierBtns.forEach((btn, i) => { 200 289 const t = tiers[i]; 201 290 const tierAmt = currency === "dkk" ? t.dkk : t.amount; 202 291 const isSelected = amount === tierAmt; 203 - const isHover = btn.down; 204 292 205 - let fillColor, borderColor, textColor; 206 - if (isSelected) { 207 - fillColor = [40, 50, 40]; 208 - borderColor = [120, 200, 120]; 209 - textColor = [220, 255, 220]; 210 - } else if (isHover) { 211 - fillColor = [35, 35, 40]; 212 - borderColor = [150, 150, 180]; 213 - textColor = [200, 200, 220]; 214 - } else { 215 - fillColor = [22, 22, 25]; 216 - borderColor = [60, 60, 65]; 217 - textColor = [140, 140, 145]; 218 - } 219 - 220 - ink(...fillColor).box(btn.box, "fill"); 221 - ink(...borderColor).box(btn.box, "outline"); 222 - 223 - // Left: price 224 - const priceText = displayAmount(tierAmt, currency); 225 - ink(...textColor).write(priceText, { 226 - x: btn.box.x + pad * 2, 227 - y: btn.box.y + pad, 228 - }); 229 - 230 - // Right: description 231 - ink(...(isSelected ? [160, 200, 160] : [90, 90, 95])).write(t.desc, { 232 - x: btn.box.x + btn.box.w - t.desc.length * charW - pad * 2, 233 - y: btn.box.y + pad, 234 - }); 293 + const selectedScheme = [[40, 50, 40], [120, 200, 120], [220, 255, 220]]; 294 + const defaultScheme = [[22, 22, 25], [60, 60, 65], [140, 140, 145]]; 295 + const hoverScheme = [[35, 35, 40], [150, 150, 180], [200, 200, 220]]; 235 296 236 - // Selection indicator 237 - if (isSelected) { 238 - ink(120, 200, 120).write(">", { 239 - x: btn.box.x + pad - 2, 240 - y: btn.box.y + pad, 241 - }); 242 - } 297 + btn.paint($, isSelected ? selectedScheme : defaultScheme, hoverScheme); 243 298 }); 244 299 245 300 // Buy button 246 301 if (buyBtn) { 247 - const buyText = getBuyText(); 248 - const isHover = buyBtn.down; 249 - const isPending = buyPending; 250 - 251 - // Recalculate width for current text 252 - const buyW = buyText.length * charW + pad * 4; 253 - buyBtn.box.w = buyW; 254 - buyBtn.box.x = floor((w - buyW) / 2); 302 + buyBtn.reposition({ center: "x", bottom: 12, screen }, getBuyText()); 255 303 256 - let fillColor, borderColor, textColor; 257 - if (isPending) { 304 + let scheme, hover; 305 + if (buyPending) { 258 306 const pulse = sin(performance.now() / 150) * 0.5 + 0.5; 259 - fillColor = [30 + pulse * 30, 40 + pulse * 20, 30]; 260 - borderColor = [150 + pulse * 105, 200 + pulse * 55, 100]; 261 - textColor = [200 + pulse * 55, 220 + pulse * 35, 180]; 307 + scheme = [ 308 + [floor(30 + pulse * 30), floor(40 + pulse * 20), 30], 309 + [floor(150 + pulse * 105), floor(200 + pulse * 55), 100], 310 + [floor(200 + pulse * 55), floor(220 + pulse * 35), 180], 311 + ]; 312 + hover = scheme; 262 313 } else if (checkoutError) { 263 - fillColor = [50, 25, 25]; 264 - borderColor = [200, 80, 80]; 265 - textColor = [255, 120, 120]; 266 - } else if (isHover) { 267 - fillColor = [40, 55, 40]; 268 - borderColor = [150, 255, 150]; 269 - textColor = [200, 255, 200]; 314 + scheme = [[50, 25, 25], [200, 80, 80], [255, 120, 120]]; 315 + hover = scheme; 270 316 } else { 271 317 const blink = sin(performance.now() / 500) * 0.3 + 0.7; 272 - fillColor = [25, 35, 25]; 273 - borderColor = [ 274 - floor(80 + blink * 70), 275 - floor(160 + blink * 95), 276 - floor(80 + blink * 70), 318 + scheme = [ 319 + [25, 35, 25], 320 + [floor(80 + blink * 70), floor(160 + blink * 95), floor(80 + blink * 70)], 321 + [180, 230, 180], 277 322 ]; 278 - textColor = [180, 230, 180]; 323 + hover = [[40, 55, 40], [150, 255, 150], [200, 255, 200]]; 279 324 } 280 - 281 - ink(...fillColor).box(buyBtn.box, "fill"); 282 - ink(...borderColor).box(buyBtn.box, "outline"); 283 - ink(...textColor).write(buyText, { 284 - x: buyBtn.box.x + pad * 2, 285 - y: buyBtn.box.y + floor(pad * 1.5), 286 - }); 325 + buyBtn.paint($, scheme, hover); 287 326 } 288 327 289 328 // Currency toggle (top right) 290 329 if (currencyBtn) { 291 - const curText = currency.toUpperCase(); 292 - const isHover = currencyBtn.down; 293 - ink(isHover ? 200 : 80).write(curText, { 294 - x: currencyBtn.box.x + pad, 295 - y: currencyBtn.box.y + floor(pad / 2), 296 - }); 330 + currencyBtn.paint($, [[12, 12, 14], [50, 50, 55], [80, 80, 85]], [[12, 12, 14], [120, 120, 130], [200, 200, 210]]); 297 331 } 298 332 299 333 // Subtle bottom line ··· 319 353 if (newAmount !== amount) { 320 354 amount = newAmount; 321 355 sound?.synth({ type: "sine", tone: 600 + i * 150, duration: 0.06, volume: 0.3 }); 356 + // Update tier labels to reflect selection 357 + tierBtns.forEach((tb, j) => tb.replaceLabel(tierText(j))); 322 358 fetchCheckout(api); 323 359 } 324 360 }, ··· 329 365 currencyBtn?.act(e, { 330 366 push: () => { 331 367 currency = currency === "usd" ? "dkk" : "usd"; 332 - // Map current amount to equivalent tier in new currency 333 368 const tierIdx = tiers.findIndex( 334 369 (t) => (currency === "usd" ? t.dkk : t.amount) === amount, 335 370 ); ··· 340 375 amount = pricing[currency].suggested; 341 376 } 342 377 sound?.synth({ type: "sine", tone: 700, duration: 0.05, volume: 0.2 }); 343 - // Update currency button size 344 - const curText = currency.toUpperCase(); 345 - const curW = curText.length * charW + pad * 2; 346 - currencyBtn.box.w = curW; 347 - currencyBtn.box.x = screen.width - curW - 8; 378 + currencyBtn.reposition({ top: 8, right: 8, screen }, currency.toUpperCase()); 379 + tierBtns.forEach((tb, j) => tb.replaceLabel(tierText(j))); 348 380 fetchCheckout(api); 349 381 }, 350 382 }); ··· 361 393 sound?.synth({ type: "sine", tone: 880, duration: 0.1, volume: 0.4 }); 362 394 jump(checkoutUrl); 363 395 } else if (checkoutError) { 364 - // Retry 365 396 checkoutError = null; 366 397 fetchCheckout(api); 367 398 sound?.synth({ type: "sine", tone: 550, duration: 0.06, volume: 0.3 });
+79 -49
system/public/aesthetic.computer/disks/prompt.mjs
··· 5751 5751 color: $.hud.currentStatusColor() || [255, 0, 200], 5752 5752 }); 5753 5753 5754 - // 🧊 Spinning wireframe cube (centered behind login buttons) 5754 + // 💻 Wireframe laptop / pizza-box (two hinged halves, centered behind login) 5755 5755 { 5756 5756 const cx = screen.width / 2; 5757 - const cy = screen.height / 2; // Centered at login button area 5757 + const cy = screen.height / 2; 5758 5758 const size = min(screen.width, screen.height) * 0.2; 5759 5759 const fov = 260; 5760 - const ay = motdFrame * 0.008; // Y-axis spin (chill) 5761 - const ax = motdFrame * 0.005; // X-axis tilt (chill) 5760 + const ay = motdFrame * 0.008; // Y-axis spin 5761 + const ax = motdFrame * 0.005; // slight X tilt 5762 5762 5763 - // Unit cube vertices centered at origin 5764 - const verts = [ 5765 - [-1,-1,-1],[1,-1,-1],[1,1,-1],[-1,1,-1], 5766 - [-1,-1, 1],[1,-1, 1],[1,1, 1],[-1,1, 1], 5763 + // Hinge angle: smoothly open and close (0 = closed, PI = flat open) 5764 + const hingeAngle = (sin(motdFrame * 0.012) * 0.5 + 0.5) * Math.PI * 0.85 + Math.PI * 0.1; 5765 + 5766 + // Half-box dimensions: wide, thin, moderate depth 5767 + const hw = 1.4; // half-width (stretched axis) 5768 + const hh = 0.08; // half-height (thin like a laptop) 5769 + const hd = 0.9; // half-depth 5770 + 5771 + // Bottom half (base) — sits flat, hinge edge at z = -hd 5772 + const base = [ 5773 + [-hw, -hh, -hd], [ hw, -hh, -hd], [ hw, hh, -hd], [-hw, hh, -hd], // back face (hinge edge) 5774 + [-hw, -hh, hd], [ hw, -hh, hd], [ hw, hh, hd], [-hw, hh, hd], // front face 5767 5775 ]; 5768 - // 12 edges 5769 - const edges = [ 5770 - [0,1],[1,2],[2,3],[3,0], // back face 5771 - [4,5],[5,6],[6,7],[7,4], // front face 5772 - [0,4],[1,5],[2,6],[3,7], // connecting 5776 + 5777 + // Top half (lid) — hinged at z = -hd, rotates around that edge 5778 + const lidLocal = [ 5779 + [-hw, -hh, 0], [ hw, -hh, 0], [ hw, hh, 0], [-hw, hh, 0], // hinge edge (z=0) 5780 + [-hw, -hh, 2 * hd], [ hw, -hh, 2 * hd], [ hw, hh, 2 * hd], [-hw, hh, 2 * hd], // far edge 5773 5781 ]; 5782 + // Rotate lid around X-axis at hinge point, then translate to hinge position 5783 + const cosH = cos(hingeAngle); 5784 + const sinH = sin(hingeAngle); 5785 + const lid = lidLocal.map(([lx, ly, lz]) => { 5786 + // Rotate around X-axis (hinge) 5787 + const ry = ly * cosH - lz * sinH; 5788 + const rz = ly * sinH + lz * cosH; 5789 + // Translate to hinge position (top of base, back edge) 5790 + return [lx, ry + hh, rz - hd]; 5791 + }); 5774 5792 5775 - // Rotate and project 5776 - const projected = verts.map(([x, y, z]) => { 5777 - // Rotate Y 5793 + // 12 edges per half 5794 + const halfEdges = [ 5795 + [0,1],[1,2],[2,3],[3,0], 5796 + [4,5],[5,6],[6,7],[7,4], 5797 + [0,4],[1,5],[2,6],[3,7], 5798 + ]; 5799 + 5800 + // Project a vertex: rotate globally then perspective-project 5801 + const project = ([x, y, z]) => { 5802 + // Rotate Y (global spin) 5778 5803 let rx = x * cos(ay) - z * sin(ay); 5779 5804 let rz = x * sin(ay) + z * cos(ay); 5780 - // Rotate X 5805 + // Rotate X (global tilt) 5781 5806 let ry = y * cos(ax) - rz * sin(ax); 5782 5807 rz = y * sin(ax) + rz * cos(ax); 5783 - // Perspective 5784 5808 const scale = fov / (fov + rz * size); 5785 5809 return [cx + rx * size * scale, cy + ry * size * scale, rz]; 5786 - }); 5810 + }; 5787 5811 5788 - // Draw edges with varied polychrome coloring (randomized per-edge hue offset) 5812 + const projBase = base.map(project); 5813 + const projLid = lid.map(project); 5814 + 5815 + // Draw edges with polychrome coloring 5789 5816 const waveOffset = motdFrame * 0.05; 5790 - edges.forEach(([a, b], i) => { 5791 - const depth = (projected[a][2] + projected[b][2]) / 2; 5792 - const brightness = 0.55 + depth * 0.25; // Back edges dimmer 5793 - // Varied hue — edges jump around the spectrum rather than sequential 5794 - const hue = ((i * 0.37 + sin(motdFrame * 0.02 + i) * 0.3) + waveOffset) % 1; 5795 - // HSV-ish to RGB (6 sector) 5796 - const sector = abs(hue) * 6; 5797 - const f = sector - floor(sector); 5798 - let r, g, bl; 5799 - const s = floor(sector) % 6; 5800 - if (s === 0) { r = 1; g = f; bl = 0; } 5801 - else if (s === 1) { r = 1 - f; g = 1; bl = 0; } 5802 - else if (s === 2) { r = 0; g = 1; bl = f; } 5803 - else if (s === 3) { r = 0; g = 1 - f; bl = 1; } 5804 - else if (s === 4) { r = f; g = 0; bl = 1; } 5805 - else { r = 1; g = 0; bl = 1 - f; } 5806 - const alpha = $.dark ? 120 : 180; 5807 - ink( 5808 - floor(r * 255 * brightness), 5809 - floor(g * 255 * brightness), 5810 - floor(bl * 255 * brightness), 5811 - alpha, 5812 - ).line( 5813 - projected[a][0], projected[a][1], 5814 - projected[b][0], projected[b][1], 5815 - ); 5816 - }); 5817 + const drawEdges = (proj, edgeOffset) => { 5818 + halfEdges.forEach(([a, b], i) => { 5819 + const depth = (proj[a][2] + proj[b][2]) / 2; 5820 + const brightness = 0.55 + depth * 0.15; 5821 + const hue = (((i + edgeOffset) * 0.37 + sin(motdFrame * 0.02 + i + edgeOffset) * 0.3) + waveOffset) % 1; 5822 + const sector = abs(hue) * 6; 5823 + const f = sector - floor(sector); 5824 + let r, g, bl; 5825 + const s = floor(sector) % 6; 5826 + if (s === 0) { r = 1; g = f; bl = 0; } 5827 + else if (s === 1) { r = 1 - f; g = 1; bl = 0; } 5828 + else if (s === 2) { r = 0; g = 1; bl = f; } 5829 + else if (s === 3) { r = 0; g = 1 - f; bl = 1; } 5830 + else if (s === 4) { r = f; g = 0; bl = 1; } 5831 + else { r = 1; g = 0; bl = 1 - f; } 5832 + const alpha = $.dark ? 120 : 180; 5833 + ink( 5834 + floor(r * 255 * brightness), 5835 + floor(g * 255 * brightness), 5836 + floor(bl * 255 * brightness), 5837 + alpha, 5838 + ).line( 5839 + proj[a][0], proj[a][1], 5840 + proj[b][0], proj[b][1], 5841 + ); 5842 + }); 5843 + }; 5817 5844 5818 - // Vertex particles — small dots at cube corners with random bright colors 5845 + drawEdges(projBase, 0); 5846 + drawEdges(projLid, 12); 5847 + 5848 + // Vertex particles at all corners 5819 5849 const particleColors = [ 5820 5850 [255, 80, 200], [80, 255, 220], [255, 255, 80], 5821 5851 [80, 200, 255], [255, 120, 80], [180, 80, 255], 5822 5852 ]; 5823 - projected.forEach(([px, py], i) => { 5853 + [...projBase, ...projLid].forEach(([px, py], i) => { 5824 5854 const pColor = particleColors[(i + floor(motdFrame * 0.04)) % particleColors.length]; 5825 5855 const flicker = 0.6 + sin(motdFrame * 0.1 + i * 1.3) * 0.4; 5826 5856 ink(...pColor, floor(flicker * ($.dark ? 150 : 200))).box(px - 1, py - 1, 2, 2);