Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

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

feat: blank.mjs product page polish + prompt curtain laptop bumper only

- Title → "AC Blank Laptop", @jeffrey highlighted in pink, drop shadows on all text
- Buy button rendered in Unifont with breathing green animation
- Manual/Paper buttons vertically stacked with distinct color schemes
- Prompt curtain top-right slot locked to laptop bumper
- publish-changelog: add description field, fix URL format
- kidlisp.com/buy: album leaderboard bar overlay + text shadow polish

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

+135 -75
+15 -7
at/publish-changelog.mjs
··· 353 353 354 354 const items = markdownToBlocks(body); 355 355 const path = slugify(title); 356 - const now = new Date().toISOString(); 356 + // Use timezone offset format (+00:00) instead of Z — matches pckt.blog UI 357 + const now = new Date().toISOString().replace(/\.\d{3}Z$/, "+00:00"); 358 + 359 + const textContent = items 360 + .map((b) => b.plaintext || "") 361 + .filter(Boolean) 362 + .join("\n\n"); 363 + 364 + // Description: first 150 chars of plain text 365 + const description = textContent.slice(0, 150); 357 366 358 367 console.log(`Publishing to change.pckt.blog...`); 359 368 console.log(` Title: ${title}`); ··· 367 376 $type: "site.standard.document", 368 377 site: PUBLICATION_URI, 369 378 title, 379 + description, 370 380 path, 371 381 publishedAt: now, 382 + updatedAt: now, 372 383 content: { 373 384 $type: "blog.pckt.content", 374 385 items, 375 386 }, 376 - textContent: items 377 - .map((b) => b.plaintext || "") 378 - .filter(Boolean) 379 - .join("\n\n"), 380 - tags: ["changelog"], 387 + textContent, 388 + tags: [], 381 389 }; 382 390 383 391 const res = await agent.com.atproto.repo.createRecord({ ··· 389 397 console.log(`\nPublished!`); 390 398 console.log(` URI: ${res.data.uri}`); 391 399 console.log(` CID: ${res.data.cid}`); 392 - console.log(` URL: https://change.pckt.blog${path}`); 400 + console.log(` URL: https://pckt.blog/b/change${path}`); 393 401 } 394 402 395 403 main().catch((err) => {
+90 -44
system/public/aesthetic.computer/disks/blank.mjs
··· 52 52 "https://download.lenovo.com/pccbbs/mobiles_pdf/tp_11e-yoga_gen6_ug_en.pdf"; 53 53 const PAPER_URL = 54 54 "https://papers.aesthetic.computer/plorking-the-planet-26-arxiv-cards.pdf"; 55 - const DESCRIPTION = 55 + const DESCRIPTION_PLAIN = 56 56 "A @jeffrey approved, refurbished Thinkpad 11e Yoga Gen 6 pre-flashed with AC Native OS and Live USB recovery stick."; 57 + const DESCRIPTION = 58 + "A \\255,100,255\\@jeffrey\\reset\\ approved, refurbished Thinkpad 11e Yoga Gen 6 pre-flashed with AC Native OS and Live USB recovery stick."; 57 59 const AUTH_TIMEOUT_MS = 1200; 58 60 59 61 async function getOptionalToken(api) { ··· 134 136 135 137 function setupButtons(ui, screen) { 136 138 buyBtn = new ui.TextButton(getBuyText(), { center: "x", bottom: 20, screen }); 137 - manualBtn = new ui.TextButton("MANUAL", { x: 6, bottom: 20, screen }); 138 - paperBtn = new ui.TextButton("PAPER", { x: 6 + manualBtn.width + 4, bottom: 20, screen }); 139 + paperBtn = new ui.TextButton("PAPER", { x: 6, bottom: 20, screen }); 140 + manualBtn = new ui.TextButton("MANUAL", { x: 6, bottom: 20 + (paperBtn.height || 14) + 4, screen }); 139 141 } 140 142 141 143 async function fetchCheckout(api) { ··· 213 215 } 214 216 } 215 217 216 - // Title + product description (centered, below HUD label) 217 - ink(fg).write("AC Blank", { center: "x", y: 24, size: 2, screen }); 218 + // Title + product description (centered, below HUD label) — with drop shadow 219 + const shadowOff = 1; 220 + const shadowAlpha = isDark ? 180 : 80; 221 + ink(0, 0, 0, shadowAlpha).write("AC Blank Laptop", { center: "x", y: 24 + shadowOff, size: 2, screen }); 222 + ink(fg).write("AC Blank Laptop", { center: "x", y: 24, size: 2, screen }); 223 + ink(0, 0, 0, shadowAlpha).write(DESCRIPTION_PLAIN, { center: "x", y: 48 + shadowOff, screen }, undefined, floor(w * 0.85)); 218 224 ink(fgDim).write(DESCRIPTION, { center: "x", y: 48, screen }, undefined, floor(w * 0.85)); 219 225 220 226 // Thanks page 221 227 if (thanks) { 222 228 const cy = floor(h / 2); 229 + ink(0, 0, 0, shadowAlpha).write("your blank is coming.", { center: "x", y: cy - 30 + shadowOff, screen }); 223 230 ink(fg).write("your blank is coming.", { center: "x", y: cy - 30, screen }); 231 + ink(0, 0, 0, shadowAlpha).write("we'll be in touch.", { center: "x", y: cy + shadowOff, screen }); 224 232 ink(fgDim).write("we'll be in touch.", { center: "x", y: cy, screen }); 225 233 return; 226 234 } ··· 714 722 } 715 723 } 716 724 717 - // Buy button 725 + // Buy button — custom rendered in Unifont for larger, more active CTA 718 726 const $btn = { ink }; 719 727 if (buyBtn) { 720 - buyBtn.reposition({ center: "x", bottom: 20, screen }, getBuyText()); 728 + const buyText = getBuyText(); 729 + // Size box for Unifont (8px wide chars, 16px tall) + padding 730 + const unifontCharW = 8, unifontH = 16, pad = 6; 731 + const boxW = buyText.length * unifontCharW + pad * 2; 732 + const boxH = unifontH + pad * 2; 733 + const boxX = floor((screen.width - boxW) / 2); 734 + const boxY = screen.height - 20 - boxH; 735 + buyBtn.btn.box.x = boxX; 736 + buyBtn.btn.box.y = boxY; 737 + buyBtn.btn.box.w = boxW; 738 + buyBtn.btn.box.h = boxH; 739 + const bx = buyBtn.btn.box; 740 + 741 + // Animated background 742 + const t = performance.now() / 1000; 743 + const isOver = buyBtn.btn.over; 744 + const isDown = buyBtn.btn.down; 721 745 722 - let scheme, hover; 723 746 if (buyPending) { 724 - const pulse = sin(performance.now() / 150) * 0.5 + 0.5; 725 - scheme = isDark 726 - ? [[floor(30 + pulse * 30), floor(40 + pulse * 20), 30], 727 - [floor(150 + pulse * 105), floor(200 + pulse * 55), 100], 728 - [floor(200 + pulse * 55), floor(220 + pulse * 35), 180]] 729 - : [[floor(200 + pulse * 30), floor(220 + pulse * 20), 200], 730 - [floor(60 + pulse * 40), floor(120 + pulse * 40), 60], 731 - [floor(30 + pulse * 20), floor(80 + pulse * 30), 30]]; 732 - hover = scheme; 747 + const pulse = sin(t * 6) * 0.5 + 0.5; 748 + const bgR = isDark ? floor(20 + pulse * 40) : floor(200 + pulse * 30); 749 + const bgG = isDark ? floor(30 + pulse * 30) : floor(220 + pulse * 20); 750 + const bgB = isDark ? 20 : 200; 751 + ink(bgR, bgG, bgB).box(bx, "fill"); 752 + const oA = floor(120 + pulse * 135); 753 + ink(isDark ? [100, 255, 100, oA] : [40, 140, 40, oA]).box(bx, "outline"); 754 + // Shadow text 755 + ink(0, 0, 0, 120).write(buyText, { x: bx.x + pad + 1, y: bx.y + pad + 1 }, undefined, undefined, false, "unifont"); 756 + ink(isDark ? [160 + floor(pulse * 95), 230, 160] : [30, floor(80 + pulse * 40), 30]) 757 + .write(buyText, { x: bx.x + pad, y: bx.y + pad }, undefined, undefined, false, "unifont"); 733 758 } else { 734 - const blink = sin(performance.now() / 500) * 0.3 + 0.7; 735 - scheme = isDark 736 - ? [[25, 35, 25], 737 - [floor(80 + blink * 70), floor(160 + blink * 95), floor(80 + blink * 70)], 738 - [180, 230, 180]] 739 - : [[220, 235, 220], 740 - [floor(40 + blink * 40), floor(100 + blink * 55), floor(40 + blink * 40)], 741 - [30, 80, 30]]; 742 - hover = isDark 743 - ? [[40, 55, 40], [150, 255, 150], [200, 255, 200]] 744 - : [[210, 230, 210], [40, 180, 40], [20, 60, 20]]; 759 + // Breathing glow animation 760 + const breath = sin(t * 2) * 0.5 + 0.5; 761 + const wave = sin(t * 3.5) * 0.3 + 0.7; 762 + let bgR, bgG, bgB; 763 + 764 + if (isDown) { 765 + bgR = isDark ? 60 : 190; bgG = isDark ? 80 : 210; bgB = isDark ? 60 : 190; 766 + } else if (isOver) { 767 + bgR = isDark ? 40 : 205; bgG = isDark ? 65 : 230; bgB = isDark ? 40 : 205; 768 + } else { 769 + bgR = isDark ? floor(20 + breath * 15) : floor(215 + breath * 15); 770 + bgG = isDark ? floor(30 + breath * 20) : floor(230 + breath * 15); 771 + bgB = isDark ? floor(20 + breath * 10) : floor(215 + breath * 10); 772 + } 773 + ink(bgR, bgG, bgB).box(bx, "fill"); 774 + 775 + // Animated outline — pulses brighter 776 + const oG = isDark ? floor(80 + wave * 120 + breath * 55) : floor(40 + wave * 80 + breath * 35); 777 + ink(isDark ? [40, oG, 40] : [30, oG, 30]).box(bx, "outline"); 778 + 779 + // Shadow text 780 + ink(0, 0, 0, isDark ? 150 : 80).write(buyText, { x: bx.x + pad + 1, y: bx.y + pad + 1 }, undefined, undefined, false, "unifont"); 781 + // Main text — breathing green 782 + const tG = isDark ? floor(160 + breath * 80 + wave * 15) : floor(20 + breath * 30); 783 + ink(isDark ? [140 + floor(breath * 60), tG, 140 + floor(breath * 40)] : [20, tG, 20]) 784 + .write(buyText, { x: bx.x + pad, y: bx.y + pad }, undefined, undefined, false, "unifont"); 745 785 } 746 - buyBtn.paint($btn, scheme, hover); 786 + $.needsPaint(); 747 787 } 748 788 749 - // Manual PDF link (bottom left) 750 - const linkScheme = isDark 751 - ? [[20, 20, 24], fgDim, [180, 180, 190]] 752 - : [[228, 228, 232], fgDim, [60, 60, 70]]; 753 - const linkHover = isDark 754 - ? [[30, 30, 38], [180, 180, 200], [220, 220, 230]] 755 - : [[215, 215, 225], [60, 60, 80], [30, 30, 40]]; 756 - if (manualBtn) { 757 - manualBtn.reposition({ x: 6, bottom: 20, screen }, "MANUAL"); 758 - manualBtn.paint($btn, linkScheme, linkHover); 759 - } 789 + // Manual + Paper links (bottom left, vertically stacked, different colors) 790 + const manualScheme = isDark 791 + ? [[20, 20, 30], [100, 140, 200], [160, 190, 240]] 792 + : [[220, 225, 240], [50, 70, 140], [30, 50, 100]]; 793 + const manualHover = isDark 794 + ? [[30, 30, 45], [140, 180, 240], [200, 220, 255]] 795 + : [[210, 215, 235], [40, 60, 160], [20, 40, 120]]; 796 + const paperScheme = isDark 797 + ? [[25, 20, 20], [200, 140, 80], [240, 190, 130]] 798 + : [[240, 230, 220], [140, 80, 30], [100, 55, 15]]; 799 + const paperHover = isDark 800 + ? [[35, 28, 28], [240, 180, 100], [255, 210, 150]] 801 + : [[235, 222, 210], [160, 100, 40], [120, 70, 20]]; 760 802 if (paperBtn) { 761 - const paperX = manualBtn ? 6 + manualBtn.width + 4 : 6; 762 - paperBtn.reposition({ x: paperX, bottom: 20, screen }, "PAPER"); 763 - paperBtn.paint($btn, linkScheme, linkHover); 803 + paperBtn.reposition({ x: 6, bottom: 20, screen }, "PAPER"); 804 + paperBtn.paint($btn, paperScheme, paperHover); 805 + } 806 + if (manualBtn) { 807 + const manualY = 20 + (paperBtn ? paperBtn.height + 4 : 0); 808 + manualBtn.reposition({ x: 6, bottom: manualY, screen }, "MANUAL"); 809 + manualBtn.paint($btn, manualScheme, manualHover); 764 810 } 765 811 } 766 812 ··· 829 875 830 876 function meta() { 831 877 return { 832 - title: "AC Blank", 878 + title: "AC Blank Laptop", 833 879 desc: "AC Native Laptop — a surplus laptop running AC Native OS.", 834 880 }; 835 881 }
+2 -1
system/public/aesthetic.computer/disks/prompt.mjs
··· 197 197 198 198 // 🎰 Top-right slot: A/B test — pick one randomly on page load 199 199 const TOP_RIGHT_BTN_CHOICES = ["give", "ad", "os", "products", "blank"]; 200 - const topRightBtnChoice = TOP_RIGHT_BTN_CHOICES[Math.floor(Math.random() * TOP_RIGHT_BTN_CHOICES.length)]; 200 + // const topRightBtnChoice = TOP_RIGHT_BTN_CHOICES[Math.floor(Math.random() * TOP_RIGHT_BTN_CHOICES.length)]; 201 + const topRightBtnChoice = "blank"; // Only show laptop bumper for now 201 202 202 203 let clearBtn; // 🧹 "Blank" button (fixed top-right, appears at 32+ chars) 203 204 let clearBtnConfirming = false; // Two-tap confirmation state
+28 -23
system/public/kidlisp.com/buy.html
··· 515 515 pointer-events: none; 516 516 } 517 517 .album-row > * { position: relative; z-index: 1; } 518 + .album-row > .album-bar { z-index: 0; } 518 519 .album-row:hover::before { 519 520 background: linear-gradient(90deg, var(--bg3) 0%, rgba(34, 30, 48, 0.85) 40%, rgba(34, 30, 48, 0.5) 100%); 520 521 } 522 + .album-bar { 523 + position: absolute; 524 + top: 0; 525 + left: 0; 526 + bottom: 0; 527 + border-radius: 0; 528 + background: var(--blue); 529 + opacity: 0.35; 530 + transition: width 0.3s ease; 531 + z-index: 0; 532 + pointer-events: none; 533 + } 534 + .album-row:first-child .album-bar { 535 + background: var(--gold); 536 + } 521 537 .album-rank { 522 538 font-size: 18px; 523 539 min-width: 28px; 524 540 text-align: center; 541 + font-weight: 900; 542 + text-shadow: 0 1px 4px rgba(0,0,0,0.7); 525 543 } 526 544 .album-name { 527 545 font-family: var(--font-fun); 528 546 font-size: 18px; 529 - font-weight: 700; 547 + font-weight: 900; 530 548 color: var(--text); 531 - flex-shrink: 0; 532 - } 533 - .album-bar-wrap { 534 549 flex: 1; 535 - display: flex; 536 - align-items: center; 537 - gap: 8px; 538 550 min-width: 0; 539 - } 540 - .album-bar { 541 - height: 20px; 542 - border-radius: 4px; 543 - background: var(--blue); 544 - transition: width 0.3s ease; 545 - min-width: 4px; 546 - } 547 - .album-row:first-child .album-bar { 548 - background: var(--gold); 551 + overflow: hidden; 552 + text-overflow: ellipsis; 553 + white-space: nowrap; 554 + text-shadow: 0 1px 4px rgba(0,0,0,0.7); 549 555 } 550 556 .album-count { 551 557 font-family: var(--font-mono); 552 - font-size: 14px; 553 - color: var(--text2); 554 - font-weight: 700; 558 + font-size: 16px; 559 + color: var(--text); 560 + font-weight: 900; 555 561 flex-shrink: 0; 562 + text-shadow: 0 1px 4px rgba(0,0,0,0.7); 556 563 } 557 564 .album-tokens { 558 565 display: none; ··· 1044 1051 const medal = i === 0 ? '\uD83E\uDD47' : i === 1 ? '\uD83E\uDD48' : i === 2 ? '\uD83E\uDD49' : `${i + 1}`; 1045 1052 return `<div class="album-entry"> 1046 1053 <div class="album-row" data-idx="${i}" style="${rowBg}"> 1054 + <div class="album-bar" style="width:${pct}%"></div> 1047 1055 <div class="album-rank">${medal}</div> 1048 1056 <div class="album-name">${name}</div> 1049 - <div class="album-bar-wrap"> 1050 - <div class="album-bar" style="width:${pct}%"></div> 1051 - </div> 1052 1057 <div class="album-count">${album.tokens.length}</div> 1053 1058 </div> 1054 1059 <div class="album-tokens" data-idx="${i}">