Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

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

blank: 6-row keyboard + trackpad, hide wireframes, restore animation

- Full 6-row ThinkPad keyboard layout (Fn row, numbers, QWERTY, home, bottom, modifiers)
- Space bar wider than other keys in modifier row
- Row indents for staggered key layout
- Centered trackpad below keyboard (~35% width)
- Remove wireframe edges and vertex particles (solid faces only)
- Restore hinge animation: closed → laptop → flat → tablet → laptop cycle

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

+64 -61
+64 -61
system/public/aesthetic.computer/disks/blank.mjs
··· 90 90 const bg = isDark ? [12, 12, 14] : [245, 243, 240]; 91 91 const fg = isDark ? 255 : 20; 92 92 const fgDim = isDark ? 120 : 100; 93 - const wireAlpha = isDark ? 140 : 120; 93 + 94 94 95 95 wipe(...bg); 96 96 ··· 121 121 const ay = frame * 0.006; 122 122 const ax = 0.3 + sin(frame * 0.003) * 0.25 + sin(frame * 0.0017) * 0.15; 123 123 124 - // DEBUG: lock hinge to 180° (flat) to verify co-planar alignment 125 - const hingeAngle = PI; 124 + // Animated hinge: cycle through closed → laptop → flat → tablet 125 + const closedAngle = 0.02; 126 + const laptopAngle = PI * 0.67; 127 + const flatAngle = PI; 128 + const tabletAngle = PI * 2 - 0.02; 129 + const keyframes = [closedAngle, laptopAngle, flatAngle, tabletAngle, laptopAngle]; 130 + const totalPhases = keyframes.length; 131 + const phaseLen = 180; 132 + const t = (frame % (totalPhases * phaseLen)) / phaseLen; 133 + const phase = floor(t); 134 + const frac = t - phase; 135 + const ease = frac < 0.5 ? 2 * frac * frac : 1 - 2 * (1 - frac) * (1 - frac); 136 + const from = keyframes[phase % totalPhases]; 137 + const to = keyframes[(phase + 1) % totalPhases]; 138 + const hingeAngle = from + ease * (to - from); 126 139 127 140 // Dimensions from spec: 293mm × 207mm × 19.9mm 128 141 const hw = 1.44, hh = 0.07, hd = 1.0; ··· 186 199 })); 187 200 } 188 201 189 - const halfEdges = [ 190 - [0, 1], [1, 2], [2, 3], [3, 0], 191 - [4, 5], [5, 6], [6, 7], [7, 4], 192 - [0, 4], [1, 5], [2, 6], [3, 7], 193 - ]; 194 - 195 202 const project = ([x, y, z]) => { 196 203 let rx = x * cos(ay) - z * sin(ay); 197 204 let rz = x * sin(ay) + z * cos(ay); ··· 260 267 let kbVisible = ke1x * ke2y - ke1y * ke2x < 0; 261 268 const kbKeys = []; 262 269 if (kbVisible) { 263 - const rows = ["QWERTYUIOP", "ASDFGHJKL", "ZXCVBNM"]; 270 + // 6-row ThinkPad keyboard layout (key counts per row) 271 + // Row 0: Fn keys (14 keys: Esc + F1-F12 + Del) 272 + // Row 1: Number row (14 keys: ` 1-0 - = Bksp) 273 + // Row 2: QWERTY (14 keys: Tab + Q-P + [ ] \) 274 + // Row 3: Home row (13 keys: Caps + A-L + ; ' Enter) 275 + // Row 4: Bottom alpha (12 keys: Shift + Z-M + , . / Shift) 276 + // Row 5: Modifier row (8 keys: Ctrl Fn Win Alt Space Alt PrtSc Ctrl) 277 + const rows = [14, 14, 14, 13, 12, 8]; 278 + const rowIndent = [0, 0, 0.02, 0.04, 0.06, 0]; 279 + // Keyboard occupies top 60% of base, trackpad below 280 + const kbFrac = 0.58; 264 281 const lerp = (a, b, t) => a + (b - a) * t; 265 282 for (let r = 0; r < rows.length; r++) { 266 - const row = rows[r]; 267 - const t0 = (r + 0.15) / rows.length, t1 = (r + 0.85) / rows.length; 268 - const indent = r * 0.03; 269 - for (let k = 0; k < row.length; k++) { 270 - const u0 = indent + (k + 0.15) / row.length * (1 - indent * 2); 271 - const u1 = indent + (k + 0.85) / row.length * (1 - indent * 2); 283 + const nKeys = rows[r]; 284 + const t0 = (r + 0.1) / rows.length * kbFrac; 285 + const t1 = (r + 0.9) / rows.length * kbFrac; 286 + const indent = rowIndent[r]; 287 + for (let k = 0; k < nKeys; k++) { 288 + // Row 5 has variable-width keys (space bar is wider) 289 + let u0, u1; 290 + if (r === 5) { 291 + // Space bar is key index 4, takes ~40% width 292 + const widths = [1, 1, 1, 1, 4.5, 1, 1, 1]; 293 + const total = widths.reduce((s, w) => s + w, 0); 294 + let start = 0; 295 + for (let j = 0; j < k; j++) start += widths[j]; 296 + u0 = (start + 0.1) / total; 297 + u1 = (start + widths[k] - 0.1) / total; 298 + } else { 299 + u0 = indent + (k + 0.1) / nKeys * (1 - indent * 2); 300 + u1 = indent + (k + 0.9) / nKeys * (1 - indent * 2); 301 + } 272 302 kbKeys.push({ 273 - type: "key", 274 303 pts: [ 275 304 [lerp(lerp(kbTL[0], kbTR[0], u0), lerp(kbBL[0], kbBR[0], u0), t0), 276 305 lerp(lerp(kbTL[1], kbTR[1], u0), lerp(kbBL[1], kbBR[1], u0), t0)], ··· 284 313 }); 285 314 } 286 315 } 287 - } 288 316 289 - // Wireframe edges 290 - const waveOffset = frame * 0.05; 291 - const addEdges = (proj, edgeOffset) => { 292 - halfEdges.forEach(([a, b], i) => { 293 - const z = (proj[a][2] + proj[b][2]) / 2; 294 - drawList.push({ z, type: "edge", proj, a, b, i, edgeOffset }); 317 + // Trackpad (centered, below keyboard, ~35% width of base) 318 + const tpU0 = 0.32, tpU1 = 0.68; 319 + const tpT0 = kbFrac + 0.05, tpT1 = 0.95; 320 + const trackpadColor = isDark ? [28, 28, 32] : [180, 180, 185]; 321 + kbKeys.push({ 322 + pts: [ 323 + [lerp(lerp(kbTL[0], kbTR[0], tpU0), lerp(kbBL[0], kbBR[0], tpU0), tpT0), 324 + lerp(lerp(kbTL[1], kbTR[1], tpU0), lerp(kbBL[1], kbBR[1], tpU0), tpT0)], 325 + [lerp(lerp(kbTL[0], kbTR[0], tpU1), lerp(kbBL[0], kbBR[0], tpU1), tpT0), 326 + lerp(lerp(kbTL[1], kbTR[1], tpU1), lerp(kbBL[1], kbBR[1], tpU1), tpT0)], 327 + [lerp(lerp(kbTL[0], kbTR[0], tpU1), lerp(kbBL[0], kbBR[0], tpU1), tpT1), 328 + lerp(lerp(kbTL[1], kbTR[1], tpU1), lerp(kbBL[1], kbBR[1], tpU1), tpT1)], 329 + [lerp(lerp(kbTL[0], kbTR[0], tpU0), lerp(kbBL[0], kbBR[0], tpU0), tpT1), 330 + lerp(lerp(kbTL[1], kbTR[1], tpU0), lerp(kbBL[1], kbBR[1], tpU0), tpT1)], 331 + ], 332 + color: trackpadColor, 295 333 }); 296 - }; 297 - addEdges(projBase, 0); 298 - addEdges(projLid, 12); 299 - for (const hv of hingeVerts) { 300 - addEdges(hv.map(project), 24); 301 334 } 302 335 303 - // Vertex particles 304 - const particleColors = [ 305 - [255, 80, 200], [80, 255, 220], [255, 255, 80], 306 - [80, 200, 255], [255, 120, 80], [180, 80, 255], 307 - ]; 308 - const allVerts = [...projBase, ...projLid]; 309 - for (const hv of hingeVerts) allVerts.push(...hv.map(project)); 310 - allVerts.forEach(([px, py, pz], i) => { 311 - drawList.push({ z: pz, type: "particle", px, py, i }); 312 - }); 313 - 314 336 // Sort back-to-front (highest z = farthest = draw first) 315 337 drawList.sort((a, b) => b.z - a.z); 316 338 ··· 323 345 ink(floor(color[0] * shade * tint[0]), floor(color[1] * shade * tint[1]), floor(color[2] * shade * tint[2])); 324 346 tri(proj[a][0], proj[a][1], proj[b][0], proj[b][1], proj[c][0], proj[c][1]); 325 347 tri(proj[a][0], proj[a][1], proj[c][0], proj[c][1], proj[d][0], proj[d][1]); 326 - } else if (item.type === "edge") { 327 - const { proj, a, b, i, edgeOffset } = item; 328 - const brightness = 0.55 + item.z * 0.15; 329 - const hue = (((i + edgeOffset) * 0.37 + sin(frame * 0.02 + i + edgeOffset) * 0.3) + waveOffset) % 1; 330 - const sector = abs(hue) * 6; 331 - const f = sector - floor(sector); 332 - let r, g, bl; 333 - const s = floor(sector) % 6; 334 - if (s === 0) { r = 1; g = f; bl = 0; } 335 - else if (s === 1) { r = 1 - f; g = 1; bl = 0; } 336 - else if (s === 2) { r = 0; g = 1; bl = f; } 337 - else if (s === 3) { r = 0; g = 1 - f; bl = 1; } 338 - else if (s === 4) { r = f; g = 0; bl = 1; } 339 - else { r = 1; g = 0; bl = 1 - f; } 340 - ink(floor(r * 255 * brightness), floor(g * 255 * brightness), floor(bl * 255 * brightness), wireAlpha) 341 - .line(proj[a][0], proj[a][1], proj[b][0], proj[b][1]); 342 - } else if (item.type === "particle") { 343 - const pColor = particleColors[(item.i + floor(frame * 0.04)) % particleColors.length]; 344 - const flicker = 0.6 + sin(frame * 0.1 + item.i * 1.3) * 0.4; 345 - ink(...pColor, floor(flicker * 150)).box(item.px - 1, item.py - 1, 2, 2); 346 348 } 347 349 } 348 350 ··· 350 352 if (kbVisible) { 351 353 for (const key of kbKeys) { 352 354 const [[x0, y0], [x1, y1], [x2, y2], [x3, y3]] = key.pts; 353 - ink(keyColor[0], keyColor[1], keyColor[2]); 355 + const c = key.color || keyColor; 356 + ink(c[0], c[1], c[2]); 354 357 tri(x0, y0, x1, y1, x2, y2); 355 358 tri(x0, y0, x2, y2, x3, y3); 356 359 }