Monorepo for Aesthetic.Computer aesthetic.computer
core SCREEN.md
3.9 kB

The Screen — a drawing guide for Aesthetic Computer pieces #

companion to HAND.md, GRAPHICS.md and papers/VOICE.md. HAND governs how the code reads, GRAPHICS how a doc draws, VOICE how the papers sound; this governs how a piece draws on the AC canvas.

it exists because of a failure mode that has no other home. a piece can be perfectly written by HAND's rules and still land wrong — a readout under the corner label, a button that eats the back tap, a layout that only fits the screen it was authored on. those are not code-style mistakes. they're screen mistakes, and until now nothing in the repo wrote them down, so every agent rediscovered them by covering the label.

the one rule #

the piece does not own the whole screen. the system draws on it too, and what the system draws is load-bearing — the label is how you know where you are and how you get back. draw around it, or hide it on purpose. never on top of it by accident.

the corner label #

the system paints it at (6, 6) in the default 6×10 typeface (lib/disk.mjs, defo = 6). one line of it occupies roughly y 6–16.

  • reserve the top-left ~20 rows. a top bar is fine if it starts below that; text at y: 10 is not.
  • the zone grows. hud.qr(url) draws a QR at (leftPad + 2, 4) and pushes the label right — same corner, much taller. swiping the label reveals share, which extends it rightward.
  • it's interactive. tapping it navigates back. a button underneath it fights the user for the same pixels and the user loses.
  • you can take it over rather than dodge it: hud.label(text, color, offset) replaces it, hud.label() hides it, hud.tinyLabel() switches to MatrixChunky8, hud.suffix(".com") adds a superscript, hud.currentLabel() reads it back.

so: readouts go along the bottom, or right-aligned. that's the default. a top bar is a decision, not a reflex.

drawing API essentials #

screen is an object: use screen.width and screen.height, never screen[0], screen[1], screen.w or screen.h. Use named colors or numeric RGB channels with ink(r, g, b); CSS hsl(...) strings are not a supported color format. circle(x, y, radius, true) draws a filled circle; omit the final flag for an outline.

paint #

  • return nothing to keep animating; return false to paint once and freeze (disk.mjs: noPaint = paintOut === false || …).
  • wipe every frame or never. a half-wiped frame reads as a bug, not as a trail — if you want a trail, draw it.

layout #

  • never hardcode a screen size. phones, desktop panes and the frameless preview are all different, and the same piece meets all three.
  • recompute geometry on reframed. that event is the only signal you get.
  • when rebuilding a ui.Button there, reassign btn.box = new geo.Box(...) — don't new ui.Button over it. a fresh button drops down/over, and anything it was holding (a note, a drag) hangs.

touch #

  • ui.Button + btn.act(e, { down, push, over, out, cancel }, pens?.()). pens() is what makes multitouch work — leave it off and the piece is one-finger-only on a phone, which is where it's being watched.
  • cancel is not optional when a button holds something. lib/ui.mjs fires it on edge-cancel and global reset; without it, held state survives a gesture that already ended.
  • over with btn.up = false is the idiom for sliding onto a pad mid-gesture.

sound #

  • a held voice is synth({ duration: "🔁", … }) and ends with .kill(fade). a fade of 0 clicks.
  • kill every voice in leave. a piece that hands off with a note still ringing rings into the next one.

the phone is the review #

pieces are pushed live to a phone as they're saved. that makes two things true: small edits beat one big rewrite, and the small screen is the real target — if the layout only works at desktop width, it doesn't work.