{"generated":"2026-08-24","posts":[{"slug":"eighty-by-twenty-five","title":"Eighty by Twenty-Five","date":"2026-08-21","updated":null,"tags":["runawaydevil","terminal","design"],"summary":"The grid on this site is a specification, not a theme. Here is what enforcing it actually cost.","art":"rd-logo","lines":["\u001b[91m██████╗ ██╗   ██╗███╗   ██╗ █████╗ ██╗    ██╗ █████╗ ██╗   ██╗\u001b[0m","\u001b[91m██╔══██╗██║   ██║████╗  ██║██╔══██╗██║    ██║██╔══██╗╚██╗ ██╔╝\u001b[0m","\u001b[31m██████╔╝██║   ██║██╔██╗ ██║███████║██║ █╗ ██║███████║ ╚████╔╝ \u001b[0m","\u001b[31m██╔══██╗██║   ██║██║╚██╗██║██╔══██║██║███╗██║██╔══██║  ╚██╔╝  \u001b[0m","\u001b[31m██║  ██║╚██████╔╝██║ ╚████║██║  ██║╚███╔███╔╝██║  ██║   ██║   \u001b[0m","\u001b[90m╚═╝  ╚═╝ ╚═════╝ ╚═╝  ╚═══╝╚═╝  ╚═╝ ╚══╝╚══╝ ╚═╝  ╚═╝   ╚═╝   \u001b[0m","\u001b[91m        ██████╗ ███████╗██╗   ██╗██╗██╗     \u001b[0m","\u001b[91m        ██╔══██╗██╔════╝██║   ██║██║██║     \u001b[0m","\u001b[31m        ██║  ██║█████╗  ██║   ██║██║██║     \u001b[0m","\u001b[31m        ██║  ██║██╔══╝  ╚██╗ ██╔╝██║██║     \u001b[0m","\u001b[31m        ██████╔╝███████╗ ╚████╔╝ ██║███████╗\u001b[0m","\u001b[90m        ╚═════╝ ╚══════╝  ╚═══╝  ╚═╝╚══════╝\u001b[0m","","  \u001b[97m\u001b[1mEIGHTY BY TWENTY-FIVE\u001b[0m","  \u001b[90m═════════════════════\u001b[0m","","  This system is 80 columns by 25 lines. Not \"about 80\". Not \"80 on desktop\".\u001b[0m","  Eighty, on a phone, on a 4K panel, in a window you have dragged to the size\u001b[0m","  of a business card. What changes with your screen is how large one cell is\u001b[0m","  drawn, and nothing else.\u001b[0m","","  That sounds like a decoration. It is closer to a constitution: almost every\u001b[0m","  awkward decision in this codebase comes from refusing to break it.\u001b[0m","","  \u001b[96mWHAT THE BROWSER WANTS TO DO INSTEAD\u001b[0m","  \u001b[90m────────────────────────────────────\u001b[0m","","  Every terminal library for the web is built around the opposite idea. You\u001b[0m","  give it a container, it measures the container, it reports back \"you have\u001b[0m","  142 columns and 38 rows\", and the program on the other end reflows to match.\u001b[0m","  That is correct for a terminal that hosts \u001b[4msomeone else's\u001b[24m program.\u001b[0m","","  Here there is no other program. The system is the only thing writing to the\u001b[0m","  screen, so the size can be fixed and everything above it can be composed\u001b[0m","  against a known width. That is why the terminal in this site is written here\u001b[0m","  rather than pulled in — not because the libraries are bad, but because their\u001b[0m","  central feature is the one thing that had to go.\u001b[0m","","  \u001b[96mNOTHING MEASURES TEXT WITH .LENGTH\u001b[0m","  \u001b[90m──────────────────────────────────\u001b[0m","","  The first real casualty was string length. \u001b[92m\"日本語\".length\u001b[37m is 3, and it\u001b[0m","  \u001b[37moccupies 6 cells. \u001b[92m\"coração\"\u001b[37m measured after Unicode normalisation can be 8\u001b[0m","  \u001b[37mcode points and 7 cells. A line with colour in it —\u001b[0m","","  \u001b[90m┌── text ──────────────────────────────────────────────────────────────────\u001b[0m","  \u001b[90m│ \u001b[92mESC[91mRUNAWAY ESC[0m\u001b[0m","  \u001b[90m└───────────────────────────────────────────────────────────────────────────\u001b[0m","","  — has fourteen characters of escape that occupy nothing at all.\u001b[0m","","  So there is exactly one module allowed to reason about horizontal space, and\u001b[0m","  it counts \u001b[4mcells\u001b[24m: grapheme clusters, East Asian widths, combining marks, and\u001b[0m","  an ANSI scanner that skips what is invisible. Everything else calls it. A\u001b[0m","  test fails if any composed screen exceeds cell 80, which is how a typo gets\u001b[0m","  caught before it becomes a crooked box.\u001b[0m","","  \u001b[96mTHREE DEFENCES, BECAUSE ONE IS NOT ENOUGH\u001b[0m","  \u001b[90m─────────────────────────────────────────\u001b[0m","","    \u001b[90m1. \u001b[0m\u001b[37m\u001b[1;97mThe model cannot exceed it.\u001b[37m\u001b[22m The screen allocates exactly 2000 cells.\u001b[0m","       \u001b[37mWriting past column 80 wraps. There is no code path that grows the\u001b[0m","       \u001b[37mgrid.\u001b[0m","    \u001b[90m2. \u001b[0m\u001b[37m\u001b[1;97mComposition measures in cells.\u001b[37m\u001b[22m Every listing, every header, every piece\u001b[0m","       \u001b[37mof art is measured before it ships. The art validator refuses to build\u001b[0m","       \u001b[37ma piece 92 cells wide.\u001b[0m","    \u001b[90m3. \u001b[0m\u001b[37m\u001b[1;97mThe renderer fits, never reflows.\u001b[37m\u001b[22m Playwright asserts \u001b[92mcols === 80\u001b[37m and\u001b[0m","       \u001b[92m\u001b[92mrows === 25\u001b[37m at 3440×1440, at 1280×720, on a tablet and on a phone.\u001b[0m","","  \u001b[96mTHE PART THAT TOOK THREE TRIES\u001b[0m","  \u001b[90m──────────────────────────────\u001b[0m","","  Filling the screen is where the constraint bites. An 80×25 grid has an\u001b[0m","  aspect ratio near 1.6:1; a modern monitor is closer to 1.9:1. Scale the grid\u001b[0m","  until it fills the height and the glyphs come out around 38 pixels tall —\u001b[0m","  legible, and absurd for reading prose. Cap the size and a wide screen shows\u001b[0m","  a lot of black.\u001b[0m","","  There is no arrangement that satisfies both. So the system does the honest\u001b[0m","  thing: it renders at the size an 80×25 terminal has always been — 640 by 400\u001b[0m","  pixels, cells of 8 by 16 — and lets the surround be black. The frame is a\u001b[0m","  hairline, so the emptiness reads as a terminal in a room rather than a page\u001b[0m","  that failed to load.\u001b[0m","","  It also stopped scaling with a CSS transform, which was quietly resampling\u001b[0m","  every glyph. Setting the font size instead means the browser rasterises at\u001b[0m","  the real size. The text got noticeably sharper the day that changed.\u001b[0m","","  \u001b[96mWHY BOTHER\u001b[0m","  \u001b[90m──────────\u001b[0m","","  Because the limit does the editing. Seventy-six usable columns is roughly\u001b[0m","  the width prose wants anyway; the constraint just makes you notice. A pager\u001b[0m","  forces an argument to have parts. Sixteen colours force contrast to mean\u001b[0m","  something.\u001b[0m","","  None of that is nostalgia. It is a set of limits that happen to produce\u001b[0m","  better decisions than no limits at all.\u001b[0m"],"filename":"2026-08-21-eighty-by-twenty-five.md"},{"slug":"a-shell-that-is-not-a-shell","title":"A Shell That Is Not a Shell","date":"2026-08-18","updated":null,"tags":["runawaydevil","security","software"],"summary":"RunawayShell has a prompt, a filesystem and a history. It has no interpreter, and that is the whole design.","art":null,"lines":["  \u001b[97m\u001b[1mA SHELL THAT IS NOT A SHELL\u001b[0m","  \u001b[90m═══════════════════════════\u001b[0m","","  The prompt at the bottom of this system looks like \u001b[92mbash\u001b[37m. It completes paths\u001b[0m","  \u001b[37mwith Tab, walks history with the arrows, understands \u001b[92mCtrl+W\u001b[37m, and prints \u001b[92mno\u001b[0m","  \u001b[92msuch file or directory\u001b[37m in the right tone of voice.\u001b[0m","","  It cannot run anything. There is no interpreter behind it at all.\u001b[0m","","  \u001b[96mWHAT ACTUALLY HAPPENS WHEN YOU PRESS ENTER\u001b[0m","  \u001b[90m──────────────────────────────────────────\u001b[0m","","  The line is split into words, honouring quotes. The first word is looked up\u001b[0m","  in a table. If it is there, that entry's function runs with the remaining\u001b[0m","  words. If it is not there, nothing happens and you get a boxed notice.\u001b[0m","","  That is the entire mechanism. No \u001b[92meval\u001b[37m, no \u001b[92mFunction\u001b[37m constructor, no dynamic\u001b[0m","  \u001b[37mdispatch on a string, no fallthrough to anything. Type\u001b[0m","","  \u001b[90m┌── text ──────────────────────────────────────────────────────────────────\u001b[0m","  \u001b[90m│ \u001b[92mguest@runawaydevil:~ $ rm -rf /\u001b[0m","  \u001b[90m└───────────────────────────────────────────────────────────────────────────\u001b[0m","","  and the parser produces four words, fails to find \u001b[92mrm\u001b[37m, and says so. \u001b[92m|\u001b[37m, \u001b[92m>\u001b[37m, \u001b[92m&&\u001b[37m\u001b[0m","  \u001b[37mand \u001b[92m$(...)\u001b[37m are not operators here — they are characters that end up inside a\u001b[0m","  \u001b[37mword and get looked up like any other.\u001b[0m","","  \u001b[96mTHE PART THAT IS EASY TO GET WRONG\u001b[0m","  \u001b[90m──────────────────────────────────\u001b[0m","","  The temptation, when building something like this, is a small convenience:\u001b[0m","  one command that takes an expression, or a debug hatch, or a plugin hook\u001b[0m","  that takes a callback name. Each is harmless alone and each one turns a\u001b[0m","  table into an interpreter.\u001b[0m","","  So the rule is written as a test:\u001b[0m","","  \u001b[90m┌── text ──────────────────────────────────────────────────────────────────\u001b[0m","  \u001b[90m│ \u001b[92mfor (const name of ['eval','exec','sh','bash','fetch','curl','wget','sudo…\u001b[0m","  \u001b[90m│ \u001b[92m  expect(findCommand(name)).toBeNull()\u001b[0m","  \u001b[90m└───────────────────────────────────────────────────────────────────────────\u001b[0m","","  \u001b[92mnode\u001b[37m exists, but it prints node status — the BBS sense of the word. The test\u001b[0m","  \u001b[37masserts that too, because a name that \u001b[4mlooks\u001b[24m dangerous deserves to be pinned\u001b[0m","  \u001b[37mdown rather than argued about.\u001b[0m","","  \u001b[96mTHERE IS NO FILESYSTEM EITHER\u001b[0m","  \u001b[90m─────────────────────────────\u001b[0m","","  \u001b[92m~/journal/2026-08-21-eighty-by-twenty-five.md\u001b[37m is not a path. It is a key in\u001b[0m","  \u001b[37ma tree of objects built when the site was compiled, from Markdown files in a\u001b[0m","  \u001b[37mrepository. \u001b[92mls\u001b[37m walks it, \u001b[92mcat\u001b[37m prints its contents, \u001b[92mgrep\u001b[37m searches them.\u001b[0m","  \u001b[37mNothing has a writer. \u001b[92m..\u001b[37m cannot climb past the root, because there is no\u001b[0m","  \u001b[37mroot to climb past — resolution is string arithmetic over an array.\u001b[0m","","  Which means the usual questions about a web shell do not apply. There is no\u001b[0m","  container to escape, no PTY to hijack, no socket to hold open, no host\u001b[0m","  process that exists. The whole system is static files and a program that\u001b[0m","  runs in your browser, and the worst thing a visitor can do to it is press\u001b[0m","  keys.\u001b[0m","","  \u001b[96mWHY KEEP THE SHAPE THEN\u001b[0m","  \u001b[90m───────────────────────\u001b[0m","","  Because the shape is good. A directory listing is a better index than a\u001b[0m","  navigation bar: it has dates, sizes, and an obvious way to look inside. \u001b[92mgrep\u001b[37m\u001b[0m","  \u001b[37mis a better search box than a search box. And muscle memory is real — people\u001b[0m","  \u001b[37mwho have typed \u001b[92mls\u001b[37m ten thousand times should get what they expect.\u001b[0m","","  The commands, the filesystem and the pager know nothing about the DOM. They\u001b[0m","  consume key events and produce ANSI. Point that stream at a socket instead\u001b[0m","  of a renderer and the same system answers a telnet call. That is not\u001b[0m","  implemented and may never be, but the seam is real and it is marked.\u001b[0m"],"filename":"2026-08-18-a-shell-that-is-not-a-shell.md"},{"slug":"nothing-is-recorded","title":"Nothing Is Recorded","date":"2026-08-11","updated":null,"tags":["runawaydevil","web","privacy"],"summary":"No analytics, no cookies, no backend, and a payload that stopped being shipped thirty-seven times.","art":null,"lines":["  \u001b[97m\u001b[1mNOTHING IS RECORDED\u001b[0m","  \u001b[90m═══════════════════\u001b[0m","","  There is nothing on the other end of this site. No database, no session\u001b[0m","  store, no analytics pipeline, no consent platform, no log of who read what.\u001b[0m","  Not because those were disabled — because they were never built, and there\u001b[0m","  is nowhere for them to run.\u001b[0m","","  That is easy to claim and cheap to check, so here is the actual accounting.\u001b[0m","","  \u001b[96mWHAT THE BROWSER STORES\u001b[0m","  \u001b[90m───────────────────────\u001b[0m","","  Six keys, all prefixed \u001b[92mrd.\u001b[37m:\u001b[0m","","  \u001b[90m┌── text ──────────────────────────────────────────────────────────────────\u001b[0m","  \u001b[90m│ \u001b[92mrd.lastVisit        the date you were last here\u001b[0m","  \u001b[90m│ \u001b[92mrd.handle           a name, if you ever set one\u001b[0m","  \u001b[90m│ \u001b[92mrd.theme            dos | amber | green | mono\u001b[0m","  \u001b[90m│ \u001b[92mrd.bootSeen         whether to play the connect sequence\u001b[0m","  \u001b[90m│ \u001b[92mrd.history          the commands you typed\u001b[0m","  \u001b[90m│ \u001b[92mrd.readerPosition   the line you left each entry on\u001b[0m","  \u001b[90m└───────────────────────────────────────────────────────────────────────────\u001b[0m","","  All of it is in your browser and none of it is sent anywhere. A test fails\u001b[0m","  if a seventh key appears, and another asserts the cookie jar is empty.\u001b[0m","","  \u001b[96mWHAT THE NETWORK DOES\u001b[0m","  \u001b[90m─────────────────────\u001b[0m","","  One HTML page, one stylesheet, one script, one font, one JSON file. After\u001b[0m","  that, a test watches every request the page makes while a session runs\u001b[0m","  commands and searches — and asserts the list is empty. The terminal\u001b[0m","  genuinely does not talk to anything once it has loaded.\u001b[0m","","  The JSON file is the interesting one, because it used to be much worse.\u001b[0m","","  \u001b[96mTHIRTY-SEVEN COPIES OF THE SAME THING\u001b[0m","  \u001b[90m─────────────────────────────────────\u001b[0m","","  The shell needs the whole collection in hand whatever route you arrived on:\u001b[0m","  \u001b[92m\u001b[92mls\u001b[37m is not a per-page question, and neither is \u001b[92msearch\u001b[37m. So the payload was\u001b[0m","  \u001b[37membedded in the page — which meant every one of the site's 37 routes carried\u001b[0m","  \u001b[37mits own copy of the same 67KB.\u001b[0m","","  \u001b[90m┌── text ──────────────────────────────────────────────────────────────────\u001b[0m","  \u001b[90m│ \u001b[92mbefore   dist 3166 KB   index.html 82 KB\u001b[0m","  \u001b[90m│ \u001b[92mafter    dist  490 KB   index.html 5.4 KB\u001b[0m","  \u001b[90m└───────────────────────────────────────────────────────────────────────────\u001b[0m","","  Two changes. The payload became one static file that every route fetches and\u001b[0m","  the browser caches once. And the plain-text field came out of it entirely —\u001b[0m","  it was 30% of the bytes and it is derivable, since stripping the escapes off\u001b[0m","  the rendered lines gives the same words. It gives them \u001b[4mbetter\u001b[24m, actually:\u001b[0m","  \u001b[92m\u001b[92mgrep\u001b[37m now matches exactly what is on the screen.\u001b[0m","","  The page is now smaller than most sites' cookie banners.\u001b[0m","","  \u001b[96mTHE FEEDS ARE THE POINT\u001b[0m","  \u001b[90m───────────────────────\u001b[0m","","  There are four, because different people read differently:\u001b[0m","","  \u001b[90m┌── text ──────────────────────────────────────────────────────────────────\u001b[0m","  \u001b[90m│ \u001b[92m/feed.xml     RSS 2.0\u001b[0m","  \u001b[90m│ \u001b[92m/feed.atom    Atom\u001b[0m","  \u001b[90m│ \u001b[92m/feed.json    JSON Feed\u001b[0m","  \u001b[90m│ \u001b[92m/sfeed.tsv    sfeed's TSV, for reading from a terminal\u001b[0m","  \u001b[90m└───────────────────────────────────────────────────────────────────────────\u001b[0m","","  All four carry every entry in full. No account, no email, no \"subscribe to\u001b[0m","  continue\". The Markdown source of any entry is at \u001b[92m/posts/<slug>/index.md\u001b[37m if\u001b[0m","  \u001b[37myou would rather have the file than the page.\u001b[0m","","  \u001b[96mAND IT WORKS WITH THE TERMINAL OFF\u001b[0m","  \u001b[90m──────────────────────────────────\u001b[0m","","  Every route renders as ordinary HTML from the same Markdown. With JavaScript\u001b[0m","  disabled you get the text, the links, the credits under the art — a plain\u001b[0m","  document. The terminal hides that fallback once it starts, and if it never\u001b[0m","  starts, the fallback is the site.\u001b[0m","","  The terminal is the interface. It is not an excuse to break the web.\u001b[0m"],"filename":"2026-08-11-nothing-is-recorded.md"},{"slug":"carta-de-um-node-solitario","title":"Carta de um node solitário","date":"2026-07-29","updated":null,"tags":["bbs","memoria","portugues"],"summary":"Sobre modems, madrugadas e a ideia de um lugar que só existe enquanto alguém está conectado.","art":null,"lines":["  \u001b[97m\u001b[1mCARTA DE UM NODE SOLITÁRIO\u001b[0m","  \u001b[90m══════════════════════════\u001b[0m","","  Havia uma hora exata em que a linha ficava livre. Depois das onze, quando\u001b[0m","  ninguém mais ia usar o telefone, o modem podia gritar à vontade. Aquele\u001b[0m","  ruído — o carrier, a negociação, o silêncio súbito quando os dois lados\u001b[0m","  finalmente concordavam — era a coisa mais próxima de uma porta se abrindo\u001b[0m","  que a informática já produziu.\u001b[0m","","  \u001b[96mUM LUGAR COM CAPACIDADE UM\u001b[0m","  \u001b[90m──────────────────────────\u001b[0m","","  O que mais me impressiona hoje não é a lentidão. É a \u001b[1m\u001b[97mexclusividade\u001b[37m\u001b[22m.\u001b[0m","","  Um BBS de um node só aceitava uma pessoa por vez. Enquanto você estava lá,\u001b[0m","  ninguém mais no mundo estava. Não havia timeline, não havia outras pessoas\u001b[0m","  rolando a mesma tela em paralelo, não havia contagem de quem estava online.\u001b[0m","  Havia você, o sysop dormindo, e um computador na sala dele.\u001b[0m","","  \u001b[90m│ \u001b[36mUm lugar que só existe enquanto alguém está conectado é uma ideia\u001b[0m\u001b[0m","  \u001b[90m│ \u001b[36mestranhamente honesta sobre o que é estar em qualquer lugar.\u001b[0m\u001b[0m","","  \u001b[96mO QUE SE FAZIA LÁ\u001b[0m","  \u001b[90m─────────────────\u001b[0m","","    \u001b[90m1. \u001b[0m\u001b[37mLer as mensagens novas desde a última visita.\u001b[0m","    \u001b[90m2. \u001b[0m\u001b[37mResponder duas ou três.\u001b[0m","    \u001b[90m3. \u001b[0m\u001b[37mBaixar um arquivo de 300 KB em quinze minutos.\u001b[0m","    \u001b[90m4. \u001b[0m\u001b[37mOlhar a arte ANSI da tela de entrada por tempo demais.\u001b[0m","    \u001b[90m5. \u001b[0m\u001b[37mDeslogar antes que a conta do telefone virasse assunto de família.\u001b[0m","","  Cinco coisas. Não havia uma sexta. A ausência de uma sexta coisa é\u001b[0m","  exatamente o que fazia as outras cinco valerem a pena.\u001b[0m","","  \u001b[96mCORAÇÃO, INFORMAÇÃO, ACENTUAÇÃO\u001b[0m","  \u001b[90m───────────────────────────────\u001b[0m","","  Uma nota técnica, já que este site insiste em ser um terminal: o português\u001b[0m","  sempre foi um pequeno problema para telas de texto. Os acentos viviam na\u001b[0m","  parte alta da tabela — \u001b[92mç\u001b[37m, \u001b[92mã\u001b[37m, \u001b[92mõ\u001b[37m, \u001b[92mé\u001b[37m —, e cada BBS decidia por conta própria se\u001b[0m","  \u001b[37mfalava CP437, CP850 ou nada disso. Você digitava \u001b[4mcoração\u001b[24m e o outro lado lia\u001b[0m","  \u001b[4;37m\u001b[4mcora‡Æo\u001b[24m.\u001b[0m","","  Aqui isso está resolvido: por dentro é tudo UTF-8, e o CP437 fica onde deve\u001b[0m","  ficar, que é no desenho.\u001b[0m","","  \u001b[96mO QUE SOBROU\u001b[0m","  \u001b[90m────────────\u001b[0m","","  Sobrou o formato. Uma tela por vez, um assunto por tela, e a certeza de que\u001b[0m","  quando você desligar aquilo tudo simplesmente deixa de existir até a próxima\u001b[0m","  madrugada.\u001b[0m","","  Não é nostalgia. É que a coisa funcionava.\u001b[0m"],"filename":"2026-07-29-carta-de-um-node-solitario.md"},{"slug":"sixteen-colours-and-a-registry","title":"Sixteen Colours and a Registry","date":"2026-07-14","updated":null,"tags":["runawaydevil","ansi","art"],"summary":"How the art in this system is made, measured, credited, and refused when the credit is missing.","art":"divider-heavy","lines":["\u001b[31m█▓▒░\u001b[0m\u001b[90m──────────────────────────────────────────────────────────────────────\u001b[0m\u001b[31m░▒▓█\u001b[0m","","  \u001b[97m\u001b[1mSIXTEEN COLOURS AND A REGISTRY\u001b[0m","  \u001b[90m══════════════════════════════\u001b[0m","","  The IBM PC text mode gave an artist sixteen foreground colours, eight\u001b[0m","  backgrounds, and 256 shapes. No pixels, no gradients, no partial\u001b[0m","  transparency. A cell was one character in one colour on one background, and\u001b[0m","  that was the whole medium. People made astonishing things with it for twenty\u001b[0m","  years.\u001b[0m","","  Everything drawn for this system works under the same rules. Here is what\u001b[0m","  that means in practice, and what the build does about it.\u001b[0m","","  \u001b[96mTHE VOCABULARY IS SMALLER THAN THE PALETTE\u001b[0m","  \u001b[90m──────────────────────────────────────────\u001b[0m","","  \u001b[90m┌── text ──────────────────────────────────────────────────────────────────\u001b[0m","  \u001b[90m│ \u001b[92m█ full block     ▓ dark shade\u001b[0m","  \u001b[90m│ \u001b[92m▒ medium shade   ░ light shade\u001b[0m","  \u001b[90m└───────────────────────────────────────────────────────────────────────────\u001b[0m","","  Those four are not textures — they are \u001b[4mmixing\u001b[24m. Light shade of white on black\u001b[0m","  is a grey that does not exist in the sixteen. Medium shade of bright blue on\u001b[0m","  blue is a blue that is not in the palette either. The entire tonal range of\u001b[0m","  the medium comes out of four characters.\u001b[0m","","  The site's own pieces are held to the CP437 character set, and the validator\u001b[0m","  says so out loud when they wander:\u001b[0m","","  \u001b[90m┌── text ──────────────────────────────────────────────────────────────────\u001b[0m","  \u001b[90m│ \u001b[92mWARN  ornaments   27x4   outside CP437: ▪ ▫ †\u001b[0m","  \u001b[90m└───────────────────────────────────────────────────────────────────────────\u001b[0m","","  Which is how \u001b[92m▪▫▪\u001b[37m became \u001b[92m■·■\u001b[37m and a dagger became \u001b[92m♦\u001b[37m. Worth knowing: CP437 has\u001b[0m","  \u001b[37mno tilde vowels. It has \u001b[92mç\u001b[37m, it has \u001b[92má\u001b[37m, it has no \u001b[92mã\u001b[37m. That is exactly why a\u001b[0m","  \u001b[37mBrazilian on a 1994 board read \u001b[4mcora‡Æo\u001b[24m instead of \u001b[4mcoração\u001b[24m — and why the\u001b[0m","  \u001b[37mwebfont here is the \"Plus\" variant rather than the pure 437 one.\u001b[0m","","  \u001b[96mTHE ART IS DATA, AND THAT IS DELIBERATE\u001b[0m","  \u001b[90m───────────────────────────────────────\u001b[0m","","  Original pieces live as typed data rather than as \u001b[92m.ANS\u001b[37m files on disk, so the\u001b[0m","  \u001b[37mcompiler and the tests can measure them with the same code that measures\u001b[0m","  \u001b[37mevery other line in the system. A piece 92 cells wide fails the build\u001b[0m","  \u001b[37minstead of arriving crooked.\u001b[0m","","  The format is not lost. \u001b[92mnpm run art:export\u001b[37m writes each piece out as a real\u001b[0m","  \u001b[92m\u001b[92m.ANS\u001b[37m: CP437 bytes, CRLF, a SAUCE record built from the registry, a\u001b[0m","  \u001b[37mterminating SUB. They open in PabloDraw and pass through AnsiLove.\u001b[0m","","  Getting that right took two bug fixes worth mentioning, because both destroy\u001b[0m","  art silently:\u001b[0m","","    \u001b[90m• \u001b[0m\u001b[37m\u001b[92mencodeCp437(' ')\u001b[37m returned \u001b[92m0x00\u001b[37m, not \u001b[92m0x20\u001b[37m. Byte zero also decodes to a\u001b[0m","      \u001b[37mblank, and it won the reverse lookup.\u001b[0m","    \u001b[90m• \u001b[0m\u001b[37mEscape, carriage return and line feed were being encoded as \u001b[92m←\u001b[37m, \u001b[92m♪\u001b[37m and \u001b[92m◙\u001b[37m.\u001b[0m","      \u001b[37mIn an art \u001b[4mbuffer\u001b[24m the low bytes are glyphs; in a \u001b[4mstream\u001b[24m they are\u001b[0m","      \u001b[37mcontrols. Confusing the two turns every colour change in a file into a\u001b[0m","      \u001b[37marrow character.\u001b[0m","","  \u001b[96mNOBODY'S WORK GOES UP WITHOUT THEIR NAME\u001b[0m","  \u001b[90m────────────────────────────────────────\u001b[0m","","  Every piece has an entry in \u001b[92msrc/art/art-registry.yml\u001b[37m: author, handle, group,\u001b[0m","  \u001b[37myear, licence, original filename, notes. A piece with no entry does not\u001b[0m","  \u001b[37mbuild. When the terms are unclear the entry says \u001b[92mreference_only: true\u001b[37m, and\u001b[0m","  \u001b[37mthe validator keeps it out of the build, out of the gallery and out of\u001b[0m","  \u001b[92m\u001b[92mdist/\u001b[37m.\u001b[0m","","  SAUCE records — the 128 bytes the scene stapled to the end of a file with\u001b[0m","  the artist's handle, group and date — are read on import and shown by \u001b[92mart\u001b[0m","  \u001b[92minfo <slug>\u001b[37m. They are the reason we still know who drew what, thirty years\u001b[0m","  \u001b[37mlater.\u001b[0m","","  There is one rule with no exception: an artist's signature is never removed.\u001b[0m","  Not to fit a layout, not to fit the grid. If the signature does not fit in\u001b[0m","  80 columns, the piece does not go in.\u001b[0m","","  \u001b[96mWHY IT STILL READS WELL\u001b[0m","  \u001b[90m───────────────────────\u001b[0m","","  A constraint that severe forces an artist to think in shape and value rather\u001b[0m","  than detail. Same discipline as woodcut, as stencil, as any printing process\u001b[0m","  with a limited number of passes. Work made under those rules tends to\u001b[0m","  survive changes in display technology, because it never depended on the\u001b[0m","  display being good.\u001b[0m","","  An ANSI drawn in 1993 on a CRT still looks correct on a 5K panel in 2026.\u001b[0m","  Very little else from 1993 can say that.\u001b[0m"],"filename":"2026-07-14-sixteen-colours-and-a-registry.md"}],"pages":[{"name":".plan","title":".plan","lines":["  Plan: runawaydevil\u001b[0m","","    Working on\u001b[0m","      - the reader: page memory per entry, so returning to a piece\u001b[0m","        returns to the line you left it on\u001b[0m","      - a proper `art info` panel with SAUCE fields laid out\u001b[0m","      - notes on modem-era software that never got written down\u001b[0m","","    Reading\u001b[0m","      - artpack NFOs, mostly for the typography\u001b[0m","      - anything about how terminals actually decided on 80 columns\u001b[0m","","    Not working on\u001b[0m","      - a redesign\u001b[0m","      - a newsletter\u001b[0m","      - an opinion about frameworks\u001b[0m","","    Last touched: 2026-08-21\u001b[0m"],"href":null},{"name":".project","title":".project","lines":["  Project: RUNAWAYDEVIL SYSTEM\u001b[0m","","    A textual system on the public web. Shell, virtual filesystem,\u001b[0m","    BBS reader, ANSI gallery. 80 columns by 25 lines, always.\u001b[0m","","    Status      running, node 01\u001b[0m","    Uptime      since 2026\u001b[0m","    Nodes       1\u001b[0m","    Users       0 registered, by design\u001b[0m","    Storage     static files\u001b[0m","    Backend     none\u001b[0m","","    Design rules\u001b[0m","      1. The grid never changes. The scale does.\u001b[0m","      2. Width is measured in cells, never in string length.\u001b[0m","      3. Nothing is recorded about a visitor. There is nowhere to record it.\u001b[0m","      4. No real shell is ever exposed. Commands come from a table.\u001b[0m","      5. Every screen must be readable with no colour at all.\u001b[0m","      6. Art carries its author's name or it does not get published.\u001b[0m","","    Ports\u001b[0m","      http   open\u001b[0m","      ssh    not yet\u001b[0m","      telnet not yet\u001b[0m"],"href":null},{"name":"about.txt","title":"About","lines":["  \u001b[97m\u001b[1mABOUT\u001b[0m","  \u001b[90m════════\u001b[0m","","  This is the personal site of \u001b[1m\u001b[97mPablo Murad\u001b[37m\u001b[22m, who writes here as \u001b[4mrunawaydevil\u001b[24m.\u001b[0m","","  It is a text system: a shell, a filesystem, a reader and a gallery, running\u001b[0m","  inside a fixed grid of 80 by 25 cells. Underneath it is a static site —\u001b[0m","  every page has a real URL, every entry has a feed item, and nothing needs\u001b[0m","  JavaScript to be read.\u001b[0m","","  \u001b[96mWHY LIKE THIS\u001b[0m","  \u001b[90m─────────────\u001b[0m","","  Because the constraints are good ones. Eighty columns forces a sentence to\u001b[0m","  be worth its width. A pager forces an argument to have parts. A palette of\u001b[0m","  sixteen colours forces contrast to mean something. None of that is\u001b[0m","  nostalgia; it is just a set of limits that happen to produce better\u001b[0m","  decisions than no limits at all.\u001b[0m","","  Also because BBSs were the first place a lot of us found other people\u001b[0m","  through a wire, and the shape of them deserves better than being a\u001b[0m","  wallpaper.\u001b[0m","","  \u001b[96mWHAT GETS WRITTEN HERE\u001b[0m","  \u001b[90m──────────────────────\u001b[0m","","  Software, mostly. Terminals, small tools, the web as it was and as it might\u001b[0m","  still be, and the occasional note in Portuguese when the subject only works\u001b[0m","  in Portuguese.\u001b[0m","","  \u001b[96mCONTACT\u001b[0m","  \u001b[90m────────\u001b[0m","","  \u001b[90m┌── text ──────────────────────────────────────────────────────────────────\u001b[0m","  \u001b[90m│ \u001b[92mweb     https://runawaydevil.com\u001b[0m","  \u001b[90m│ \u001b[92mfeed    https://runawaydevil.com/feed.xml\u001b[0m","  \u001b[90m└───────────────────────────────────────────────────────────────────────────\u001b[0m","","  \u001b[96mCOLOPHON\u001b[0m","  \u001b[90m────────\u001b[0m","","  Built with Astro and TypeScript. The terminal, the ANSI parser, the shell,\u001b[0m","  the Markdown-to-textmode renderer and the art are written for this site\u001b[0m","  rather than pulled in; the details are in \u001b[92msystem/\u001b[37m.\u001b[0m","","  Type \u001b[92msystem\u001b[37m for the full colophon, or \u001b[92mfinger\u001b[37m for the short version.\u001b[0m"],"href":"/about"},{"name":"links.txt","title":"Links","lines":["  \u001b[97m\u001b[1mLINKS\u001b[0m","  \u001b[90m════════\u001b[0m","","  Places worth going instead of here. Nothing is affiliated with anything.\u001b[0m","","  \u001b[96mTHE ARTSCENE\u001b[0m","  \u001b[90m────────────\u001b[0m","","    \u001b[90m• \u001b[0m\u001b[37m\u001b[4;96m16colo.rs\u001b[24m\u001b[90m[1]\u001b[37m — the artpack archive. Packs, artists, groups, and the\u001b[0m","      \u001b[37mactual \u001b[92m.ANS\u001b[37m files, browsable.\u001b[0m","    \u001b[90m• \u001b[0m\u001b[37m\u001b[4;96martpacks.org\u001b[24m\u001b[90m[2]\u001b[37m — the other archive, with a different index of the same\u001b[0m","      \u001b[37mhistory.\u001b[0m","    \u001b[90m• \u001b[0m\u001b[37m\u001b[4;96mASCII Art Archive\u001b[24m\u001b[90m[3]\u001b[37m — plain ASCII by category, and a good place to see\u001b[0m","      \u001b[37mhow a style develops.\u001b[0m","    \u001b[90m• \u001b[0m\u001b[37m\u001b[4;96martscene.textfiles.com\u001b[24m\u001b[90m[4]\u001b[37m — the scene's own papers, groups and ephemera.\u001b[0m","    \u001b[90m• \u001b[0m\u001b[37m\u001b[4;96mascii.textfiles.com\u001b[24m\u001b[90m[5]\u001b[37m — collected ASCII, mostly from the BBS era.\u001b[0m","","  \u001b[96mDOCUMENTATION WORTH KEEPING\u001b[0m","  \u001b[90m───────────────────────────\u001b[0m","","    \u001b[90m• \u001b[0m\u001b[37m\u001b[4;96mSAUCE specification\u001b[24m\u001b[90m[6]\u001b[37m — the 128 bytes that carry an artist's name to\u001b[0m","      \u001b[37mthe present day.\u001b[0m","    \u001b[90m• \u001b[0m\u001b[37m\u001b[4;96mtextfiles.com\u001b[24m\u001b[90m[7]\u001b[37m — twenty years of what people actually wrote to each\u001b[0m","      \u001b[37mother before the web.\u001b[0m","    \u001b[90m• \u001b[0m\u001b[37m\u001b[4;96mOldschool PC Font Resource\u001b[24m\u001b[90m[8]\u001b[37m — the hardware fonts, documented and\u001b[0m","      \u001b[37mrecovered, with their licences stated.\u001b[0m","","  \u001b[96mTOOLS\u001b[0m","  \u001b[90m────────\u001b[0m","","    \u001b[90m• \u001b[0m\u001b[37m\u001b[4;96mMoebius\u001b[24m\u001b[90m[9]\u001b[37m — the modern ANSI editor.\u001b[0m","    \u001b[90m• \u001b[0m\u001b[37m\u001b[4;96mPabloDraw\u001b[24m\u001b[90m[10]\u001b[37m — the other one, with collaborative editing.\u001b[0m","    \u001b[90m• \u001b[0m\u001b[37m\u001b[4;96mDurdraw\u001b[24m\u001b[90m[11]\u001b[37m — ASCII and ANSI editing in a terminal, Unicode aware.\u001b[0m","    \u001b[90m• \u001b[0m\u001b[37m\u001b[4;96mAnsiLove\u001b[24m\u001b[90m[12]\u001b[37m — renders \u001b[92m.ANS\u001b[37m to images and reads SAUCE. Used here in\u001b[0m","      \u001b[37mdevelopment to check widths.\u001b[0m","    \u001b[90m• \u001b[0m\u001b[37m\u001b[4;96mFIGlet\u001b[24m\u001b[90m[13]\u001b[37m — large letters out of ordinary ones.\u001b[0m","","  \u001b[96mTERMINALS IN BROWSERS\u001b[0m","  \u001b[90m─────────────────────\u001b[0m","","    \u001b[90m• \u001b[0m\u001b[37m\u001b[4;96mghostty-web\u001b[24m\u001b[90m[14]\u001b[37m — Ghostty's engine, compiled for the browser.\u001b[0m","    \u001b[90m• \u001b[0m\u001b[37m\u001b[4;96mwterm\u001b[24m\u001b[90m[15]\u001b[37m — a terminal component built around running processes in a\u001b[0m","      \u001b[37mpage.\u001b[0m","    \u001b[90m• \u001b[0m\u001b[37m\u001b[4;96mjust-bash\u001b[24m\u001b[90m[16]\u001b[37m — a Bash interpreter in JavaScript, which is a different\u001b[0m","      \u001b[37mproblem than it sounds like.\u001b[0m","","  \u001b[90m────────────────────────────────────────────────────────────────────────────\u001b[0m","  \u001b[97mLINKS\u001b[0m","","  \u001b[90m[1] \u001b[0m\u001b[96mhttps://16colo.rs\u001b[0m\u001b[0m","  \u001b[90m[2] \u001b[0m\u001b[96mhttps://artpacks.org\u001b[0m\u001b[0m","  \u001b[90m[3] \u001b[0m\u001b[96mhttps://asciiart.eu\u001b[0m\u001b[0m","  \u001b[90m[4] \u001b[0m\u001b[96mhttps://artscene.textfiles.com\u001b[0m\u001b[0m","  \u001b[90m[5] \u001b[0m\u001b[96mhttps://ascii.textfiles.com\u001b[0m\u001b[0m","  \u001b[90m[6] \u001b[0m\u001b[96mhttps://www.acid.org/info/sauce/sauce.htm\u001b[0m\u001b[0m","  \u001b[90m[7] \u001b[0m\u001b[96mhttp://textfiles.com\u001b[0m\u001b[0m","  \u001b[90m[8] \u001b[0m\u001b[96mhttps://int10h.org/oldschool-pc-fonts/\u001b[0m\u001b[0m","  \u001b[90m[9] \u001b[0m\u001b[96mhttps://github.com/blocktronics/moebius\u001b[0m\u001b[0m","  \u001b[90m[10] \u001b[0m\u001b[96mhttps://github.com/cwensley/pablodraw\u001b[0m\u001b[0m","  \u001b[90m[11] \u001b[0m\u001b[96mhttps://github.com/durdraw/durdraw\u001b[0m\u001b[0m","  \u001b[90m[12] \u001b[0m\u001b[96mhttps://github.com/ansilove/ansilove\u001b[0m\u001b[0m","  \u001b[90m[13] \u001b[0m\u001b[96mhttp://www.figlet.org\u001b[0m\u001b[0m","  \u001b[90m[14] \u001b[0m\u001b[96mhttps://github.com/coder/ghostty-web\u001b[0m\u001b[0m","  \u001b[90m[15] \u001b[0m\u001b[96mhttps://github.com/vercel-labs/wterm\u001b[0m\u001b[0m","  \u001b[90m[16] \u001b[0m\u001b[96mhttps://github.com/vercel-labs/just-bash\u001b[0m\u001b[0m"],"href":"/links"},{"name":"README","title":"README","lines":["  \u001b[97m\u001b[1mREADME\u001b[0m","  \u001b[90m════════\u001b[0m","","  RUNAWAYDEVIL SYSTEM — NODE 01\u001b[0m","","  You are connected to a text system. It has 80 columns and 25 lines, and it\u001b[0m","  will keep having 80 columns and 25 lines regardless of the size of the\u001b[0m","  window you opened it in. That is not a limitation being worked around. That\u001b[0m","  is the specification.\u001b[0m","","  \u001b[96mGETTING AROUND\u001b[0m","  \u001b[90m──────────────\u001b[0m","","  \u001b[90m┌── text ──────────────────────────────────────────────────────────────────\u001b[0m","  \u001b[90m│ \u001b[92mhelp              the list of commands\u001b[0m","  \u001b[90m│ \u001b[92mls                what is in this directory\u001b[0m","  \u001b[90m│ \u001b[92mcd journal        go somewhere\u001b[0m","  \u001b[90m│ \u001b[92mread <name>       open something in the reader\u001b[0m","  \u001b[90m│ \u001b[92mlatest            the most recent entry\u001b[0m","  \u001b[90m│ \u001b[92msearch <term>     look for a word\u001b[0m","  \u001b[90m│ \u001b[92mart               the gallery\u001b[0m","  \u001b[90m└───────────────────────────────────────────────────────────────────────────\u001b[0m","","  Arrow keys walk the command history. Tab completes paths and command names.\u001b[0m","  Ctrl+L clears. Ctrl+C abandons the line you are typing. In the reader, N and\u001b[0m","  P turn pages, and Q leaves.\u001b[0m","","  \u001b[96mWHAT IS HERE\u001b[0m","  \u001b[90m────────────\u001b[0m","","  \u001b[90m┌── text ──────────────────────────────────────────────────────────────────\u001b[0m","  \u001b[90m│ \u001b[92mjournal/     entries, newest first\u001b[0m","  \u001b[90m│ \u001b[92mart/         textmode work, ours and credited\u001b[0m","  \u001b[90m│ \u001b[92mfiles/       the NFO and the description file\u001b[0m","  \u001b[90m│ \u001b[92mlinks/       places worth going instead of here\u001b[0m","  \u001b[90m│ \u001b[92msystem/      what this thing is made of\u001b[0m","  \u001b[90m│ \u001b[92mabout.txt    who is responsible\u001b[0m","  \u001b[90m│ \u001b[92m.plan        what is being worked on\u001b[0m","  \u001b[90m└───────────────────────────────────────────────────────────────────────────\u001b[0m","","  \u001b[96mWHAT IS NOT HERE\u001b[0m","  \u001b[90m────────────────\u001b[0m","","  No account. No cookie. No tracking. No newsletter. No analytics. Nothing is\u001b[0m","  recorded about this visit anywhere, because there is nowhere to record it:\u001b[0m","  the whole system is static files and a program that runs in your browser.\u001b[0m","","  There is no shell behind this. Commands are matched against a table;\u001b[0m","  anything not in the table is refused. The filesystem you are looking at is a\u001b[0m","  data structure, not a disk.\u001b[0m","","  No warranty. No tracking. No reason to be here. Stay if you like.\u001b[0m"],"href":"/readme"},{"name":"system.txt","title":"System","lines":["  \u001b[97m\u001b[1mSYSTEM\u001b[0m","  \u001b[90m════════\u001b[0m","","  What this thing is made of, for anyone who wants to know whether it is real.\u001b[0m","","  \u001b[96mTHE GRID\u001b[0m","  \u001b[90m────────\u001b[0m","","  \u001b[90m┌── text ──────────────────────────────────────────────────────────────────\u001b[0m","  \u001b[90m│ \u001b[92mcolumns   80     fixed\u001b[0m","  \u001b[90m│ \u001b[92mrows      25     fixed\u001b[0m","  \u001b[90m│ \u001b[92mcells     2000\u001b[0m","  \u001b[90m│ \u001b[92mcell      8x16   pixels, at the font's native size\u001b[0m","  \u001b[90m│ \u001b[92mpalette   16\u001b[0m","  \u001b[90m└───────────────────────────────────────────────────────────────────────────\u001b[0m","","  The grid does not change. On a 4K panel and on a phone the system is 80 by\u001b[0m","  25; what changes is how many device pixels one cell occupies. There is no\u001b[0m","  fit function anywhere in this codebase that reports a column count back from\u001b[0m","  the viewport, and there is a test that fails if a screen ever composes past\u001b[0m","  cell 80.\u001b[0m","","  The fit is done by choosing a font size, not by scaling: a transform would\u001b[0m","  resample every glyph, and the text is noticeably sharper without one.\u001b[0m","","  The grid fills the window. It cannot fill it exactly — 80x25 is proportioned\u001b[0m","  about 1.6 to 1 and a monitor is nearer 1.9 — and no single size both fills\u001b[0m","  the screen and keeps the type small, so that trade is yours:\u001b[0m","","  \u001b[90m┌── text ──────────────────────────────────────────────────────────────────\u001b[0m","  \u001b[90m│ \u001b[92mzoom out     one step smaller\u001b[0m","  \u001b[90m│ \u001b[92mzoom in      one step larger\u001b[0m","  \u001b[90m│ \u001b[92mzoom 70      seventy percent of the window\u001b[0m","  \u001b[90m│ \u001b[92mzoom reset   fill it again\u001b[0m","  \u001b[90m└───────────────────────────────────────────────────────────────────────────\u001b[0m","","  The choice is remembered. What never changes is the 80 by 25.\u001b[0m","","  \u001b[96mTHE FONT\u001b[0m","  \u001b[90m────────\u001b[0m","","  \u001b[92mWebPlus IBM VGA 8x16\u001b[37m, from The Ultimate Oldschool PC Font Pack by \u001b[1m\u001b[97mVileR\u001b[37m\u001b[22m\u001b[0m","  \u001b[37m(int10h.org), under CC BY-SA 4.0 and redistributed unmodified. It is the\u001b[0m","  \u001b[37mface an IBM PC drew text with in 80x25 mode, recovered and reconstructed\u001b[0m","  \u001b[37mfrom the hardware.\u001b[0m","","  The \"Plus\" charset rather than the pure CP437 one, for a specific reason:\u001b[0m","  CP437 has \u001b[92mç\u001b[37m and \u001b[92má\u001b[37m but no \u001b[92mã\u001b[37m. That is why a Brazilian reading a board in 1994\u001b[0m","  \u001b[37msaw \u001b[4mcora‡Æo\u001b[24m, and it is not a bug worth reproducing.\u001b[0m","","  \u001b[96mWIDTH\u001b[0m","  \u001b[90m────────\u001b[0m","","  Nothing here measures text with \u001b[92mstring.length\u001b[37m. Horizontal space is measured\u001b[0m","  \u001b[37min cells, taking account of grapheme clusters, combining marks, East Asian\u001b[0m","  \u001b[37mdouble-width characters, and ANSI escape sequences that occupy no space at\u001b[0m","  \u001b[37mall. That is why \u001b[4mcoração\u001b[24m and 日本語 both land where they should.\u001b[0m","","  \u001b[96mTHE PARSER\u001b[0m","  \u001b[90m──────────\u001b[0m","","  The screen is fed an ANSI byte stream and understands the subset that this\u001b[0m","  system emits and that classic \u001b[92m.ANS\u001b[37m art uses: SGR colour and attributes,\u001b[0m","  \u001b[37mcursor movement, erase in line and display, save and restore. Anything it\u001b[0m","  \u001b[37mdoes not recognise is dropped rather than guessed at.\u001b[0m","","  \u001b[96mTHE SHELL\u001b[0m","  \u001b[90m─────────\u001b[0m","","  \u001b[92mRunawayShell\u001b[37m. Commands are entries in a table. There is no \u001b[92meval\u001b[37m, no host\u001b[0m","  \u001b[37mprocess, no socket, and no path from anything typed here to anything running\u001b[0m","  \u001b[37manywhere. The filesystem is a tree of objects built when the site was\u001b[0m","  \u001b[37mcompiled.\u001b[0m","","  If a command is not in the table, the answer is no.\u001b[0m","","  \u001b[96mTHE CONTENT\u001b[0m","  \u001b[90m───────────\u001b[0m","","  Markdown files in a repository, parsed to an AST at build time and rendered\u001b[0m","  to terminal lines: headings become rules, lists become bullets with hanging\u001b[0m","  indents, links become numbered references with a table at the end, code\u001b[0m","  becomes a railed block that is never re-wrapped.\u001b[0m","","  \u001b[96mTHE ART\u001b[0m","  \u001b[90m────────\u001b[0m","","  Original textmode work, authored as data so that its width can be checked by\u001b[0m","  the same code that measures everything else. Third-party pieces carry a\u001b[0m","  registry entry naming the artist, their group and the terms — and if the\u001b[0m","  terms are unclear, the piece is marked reference-only and does not ship.\u001b[0m","","  SAUCE records are read and kept.\u001b[0m","","  \u001b[96mUNDERNEATH\u001b[0m","  \u001b[90m──────────\u001b[0m","","  A static site. Every entry has its own URL and its own HTML, every list has\u001b[0m","  a feed, and the sitemap is real. The terminal is the interface, not an\u001b[0m","  excuse to break the web.\u001b[0m","","  \u001b[90m┌── text ──────────────────────────────────────────────────────────────────\u001b[0m","  \u001b[90m│ \u001b[92mastro        static build, real routes\u001b[0m","  \u001b[90m│ \u001b[92mtypescript   the whole system\u001b[0m","  \u001b[90m│ \u001b[92mno backend   nothing to run, nothing to breach\u001b[0m","  \u001b[90m│ \u001b[92mno analytics nothing to collect\u001b[0m","  \u001b[90m└───────────────────────────────────────────────────────────────────────────\u001b[0m","","  \u001b[96mTHEMES\u001b[0m","  \u001b[90m────────\u001b[0m","","  \u001b[90m┌── text ──────────────────────────────────────────────────────────────────\u001b[0m","  \u001b[90m│ \u001b[92mtheme dos     the default: IBM colours on black\u001b[0m","  \u001b[90m│ \u001b[92mtheme amber   monochrome amber\u001b[0m","  \u001b[90m│ \u001b[92mtheme green   monochrome P1 phosphor\u001b[0m","  \u001b[90m│ \u001b[92mtheme mono    white on black, no colour at all\u001b[0m","  \u001b[90m└───────────────────────────────────────────────────────────────────────────\u001b[0m","","  Every screen is designed to be legible in \u001b[92mmono\u001b[37m. Colour is emphasis here, not\u001b[0m","  \u001b[37minformation.\u001b[0m"],"href":"/system"},{"name":"FILE_ID.DIZ","title":"FILE_ID.DIZ","lines":["RUNAWAYDEVIL SYSTEM .................. 01","","  A text system on the public web:","  shell, virtual filesystem, BBS","  reader, ANSI gallery. 80x25, fixed.","","  No accounts. No tracking. No backend.","  Keyboard only. Feeds included.","","  runawaydevil.com ........... node 01"],"href":null},{"name":"RUNAWAY.NFO","title":"RUNAWAY.NFO","lines":["   ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄","   ░▒▓█  R U N A W A Y D E V I L   S Y S T E M  █▓▒░   n o d e   0 1","   ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀","","╔════════════════════════════════════════════════════════════════════════╗","║                        R U N A W A Y D E V I L                         ║","║                      a textual system, on purpose                      ║","╠════════════════════════════════════════════════════════════════════════╣","║                                                                        ║","║   SYSTEM      RUNAWAYDEVIL / NODE 01                                   ║","║   ADDRESS     runawaydevil.com                                         ║","║   GRID        80 columns x 25 lines, fixed                             ║","║   ENCODING    UTF-8 inside, CP437 as the visual language               ║","║   COLOURS     16, the classic palette                                  ║","║   BACKEND     none. static files and a program in your browser         ║","║   ACCOUNTS    none                                                     ║","║   TRACKING    none                                                     ║","║                                                                        ║","╠════════════════════════════════════════════════════════════════════════╣","║                                                                        ║","║   CODE        Pablo Murad  ·  runawaydevil                             ║","║   ART         original textmode work for this system                   ║","║   CREDITS     every imported piece carries its artist's name           ║","║               in src/art/art-registry.yml, or it is not here           ║","║                                                                        ║","║   GREETS      everyone who kept a node running after midnight,         ║","║               and everyone still publishing on their own page          ║","║                                                                        ║","╠════════════════════════════════════════════════════════════════════════╣","║                                                                        ║","║   No warranty. No tracking. No reason to be here.                      ║","║   Stay if you like.                                                    ║","║                                                                        ║","╚════════════════════════════════════════════════════════════════════════╝","","   ░▒▓█ EOF █▓▒░"],"href":null}],"art":[{"slug":"404","title":"Not found","category":"screens","lines":["\u001b[31m ██╗  ██╗ ██████╗ ██╗  ██╗\u001b[0m","\u001b[31m ██║  ██║██╔═████╗██║  ██║\u001b[0m","\u001b[91m ███████║██║██╔██║███████║\u001b[0m","\u001b[91m ╚════██║████╔╝██║╚════██║\u001b[0m","\u001b[90m      ██║╚██████╔╝     ██║\u001b[0m","\u001b[90m      ╚═╝ ╚═════╝      ╚═╝\u001b[0m","","\u001b[37m No such file or directory.\u001b[0m","\u001b[90m The system has no record of it and does not speculate.\u001b[0m"],"width":55,"height":9,"credit":"Pablo Murad (runawaydevil) · 2026","author":"Pablo Murad","license":"© 2026 Pablo Murad. Original work for runawaydevil.com; reuse by permission.","notes":"Numerals in the same letterform as the wordmark.","source":"original","year":2026,"sauce":null},{"slug":"boot-logo","title":"Boot mark","category":"logos","lines":["\u001b[31m  ▄▄▄▄▄   \u001b[0m\u001b[90m▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄\u001b[0m","\u001b[91m ███████  \u001b[0m\u001b[37mR U N A W A Y D E V I L\u001b[0m","\u001b[31m ██▀ ▀██  \u001b[0m\u001b[90msystem \u001b[0m\u001b[36mnode 01\u001b[0m","\u001b[31m ▀▀   ▀▀  \u001b[0m\u001b[90m▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀\u001b[0m"],"width":46,"height":4,"credit":"Pablo Murad (runawaydevil) · 2026","author":"Pablo Murad","license":"© 2026 Pablo Murad. Original work for runawaydevil.com; reuse by permission.","notes":"Four-row mark for the connect sequence.","source":"original","year":2026,"sauce":null},{"slug":"devil","title":"Devil motif","category":"logos","lines":["\u001b[31m   ▄▄       ▄▄   \u001b[0m","\u001b[31m   ██▄▄▄▄▄▄▄██   \u001b[0m","\u001b[91m  ▄███████████▄  \u001b[0m","\u001b[91m  ██\u001b[0m\u001b[93m▀▀\u001b[0m\u001b[91m███\u001b[0m\u001b[93m▀▀\u001b[0m\u001b[91m██  \u001b[0m","\u001b[91m  ███████████████\u001b[0m","\u001b[31m   ▀█\u001b[0m\u001b[90m▄▀▄▀▄▀▄\u001b[0m\u001b[31m█▀   \u001b[0m","\u001b[31m     ▀▀▀▀▀▀▀     \u001b[0m"],"width":17,"height":7,"credit":"Pablo Murad (runawaydevil) · 2026","author":"Pablo Murad","license":"© 2026 Pablo Murad. Original work for runawaydevil.com; reuse by permission.","notes":"Horned skull in half-blocks. The house motif.","source":"original","year":2026,"sauce":null},{"slug":"divider-dots","title":"Dotted divider","category":"gallery","lines":["\u001b[90m·  ·  ·  ·  ·  ·  ·  ·  ·  ·  ·  ·  ·  ·  ·  ·  ·  ·  ·  ·  ·  ·  ·  ·  ·  ·  \u001b[0m"],"width":78,"height":1,"credit":"Pablo Murad (runawaydevil) · 2026","author":"Pablo Murad","license":"© 2026 Pablo Murad. Original work for runawaydevil.com; reuse by permission.","notes":"Interpunct rhythm, 78 cells.","source":"original","year":2026,"sauce":null},{"slug":"divider-heavy","title":"Heavy divider","category":"gallery","lines":["\u001b[31m█▓▒░\u001b[0m\u001b[90m──────────────────────────────────────────────────────────────────────\u001b[0m\u001b[31m░▒▓█\u001b[0m"],"width":78,"height":1,"credit":"Pablo Murad (runawaydevil) · 2026","author":"Pablo Murad","license":"© 2026 Pablo Murad. Original work for runawaydevil.com; reuse by permission.","notes":"Shade ramp at both ends, rule between.","source":"original","year":2026,"sauce":null},{"slug":"divider-light","title":"Light divider","category":"gallery","lines":["\u001b[90m──────────────────────────────────────────────────────────────────────────────\u001b[0m"],"width":78,"height":1,"credit":"Pablo Murad (runawaydevil) · 2026","author":"Pablo Murad","license":"© 2026 Pablo Murad. Original work for runawaydevil.com; reuse by permission.","notes":"Single rule, 78 cells.","source":"original","year":2026,"sauce":null},{"slug":"gallery-header","title":"Gallery header","category":"screens","lines":["\u001b[90m── \u001b[0m\u001b[97m\u001b[44m GALLERY \u001b[0m\u001b[90m ─────────────────────────────────────────────────────────────────\u001b[0m"],"width":78,"height":1,"credit":"Pablo Murad (runawaydevil) · 2026","author":"Pablo Murad","license":"© 2026 Pablo Murad. Original work for runawaydevil.com; reuse by permission.","notes":"Same construction as the journal header, blue tag.","source":"original","year":2026,"sauce":null},{"slug":"huh","title":"Unknown command","category":"screens","lines":["  \u001b[90m┌──────────────────────────────────────────────┐\u001b[0m","  \u001b[90m│ \u001b[0m\u001b[91m?\u001b[0m\u001b[37m Command not recognised.\u001b[0m                   \u001b[90m │\u001b[0m","  \u001b[90m│ \u001b[0m\u001b[90mType \u001b[0m\u001b[96mhelp\u001b[0m\u001b[90m for the list of what exists.\u001b[0m      \u001b[90m │\u001b[0m","  \u001b[90m└──────────────────────────────────────────────┘\u001b[0m"],"width":50,"height":4,"credit":"Pablo Murad (runawaydevil) · 2026","author":"Pablo Murad","license":"© 2026 Pablo Murad. Original work for runawaydevil.com; reuse by permission.","notes":"Boxed notice shown when the parser does not recognise a verb.","source":"original","year":2026,"sauce":null},{"slug":"journal-header","title":"Journal header","category":"screens","lines":["\u001b[90m── \u001b[0m\u001b[97m\u001b[41m JOURNAL \u001b[0m\u001b[90m ─────────────────────────────────────────────────────────────────\u001b[0m"],"width":78,"height":1,"credit":"Pablo Murad (runawaydevil) · 2026","author":"Pablo Murad","license":"© 2026 Pablo Murad. Original work for runawaydevil.com; reuse by permission.","notes":"Reverse-video tag on a rule.","source":"original","year":2026,"sauce":null},{"slug":"logoff","title":"Logoff screen","category":"screens","lines":["        \u001b[90m┌──────────────────────────────────────────┐\u001b[0m","        \u001b[90m│ \u001b[0m\u001b[91mNO CARRIER\u001b[0m                              \u001b[90m │\u001b[0m","        \u001b[90m│ \u001b[0m                                        \u001b[90m │\u001b[0m","        \u001b[90m│ \u001b[0m\u001b[37mConnection closed by remote host.\u001b[0m       \u001b[90m │\u001b[0m","        \u001b[90m│ \u001b[0m\u001b[90mNothing was recorded.\u001b[0m                   \u001b[90m │\u001b[0m","        \u001b[90m└──────────────────────────────────────────┘\u001b[0m"],"width":52,"height":6,"credit":"Pablo Murad (runawaydevil) · 2026","author":"Pablo Murad","license":"© 2026 Pablo Murad. Original work for runawaydevil.com; reuse by permission.","notes":"NO CARRIER panel.","source":"original","year":2026,"sauce":null},{"slug":"ornaments","title":"Ornament set","category":"gallery","lines":["\u001b[90m  \u001b[0m\u001b[31m◄►\u001b[0m\u001b[90m  ─── \u001b[0m\u001b[31m·\u001b[0m\u001b[90m ─── \u001b[0m\u001b[31m◄►\u001b[0m","\u001b[90m  \u001b[0m\u001b[31m■·■\u001b[0m\u001b[90m ══════════════ \u001b[0m\u001b[31m■·■\u001b[0m","\u001b[90m  \u001b[0m\u001b[31m╓─╥─╖\u001b[0m\u001b[90m  ▄▀▄▀▄▀▄▀▄▀▄  \u001b[0m\u001b[31m╓─╥─╖\u001b[0m","\u001b[90m  \u001b[0m\u001b[91m♦\u001b[0m\u001b[90m ·············· \u001b[0m\u001b[91m♦\u001b[0m"],"width":27,"height":4,"credit":"Pablo Murad (runawaydevil) · 2026","author":"Pablo Murad","license":"© 2026 Pablo Murad. Original work for runawaydevil.com; reuse by permission.","notes":"Four small rules for section breaks. CP437-safe characters only.","source":"original","year":2026,"sauce":null},{"slug":"rd-logo","title":"RUNAWAYDEVIL wordmark","category":"logos","lines":["\u001b[91m██████╗ ██╗   ██╗███╗   ██╗ █████╗ ██╗    ██╗ █████╗ ██╗   ██╗\u001b[0m","\u001b[91m██╔══██╗██║   ██║████╗  ██║██╔══██╗██║    ██║██╔══██╗╚██╗ ██╔╝\u001b[0m","\u001b[31m██████╔╝██║   ██║██╔██╗ ██║███████║██║ █╗ ██║███████║ ╚████╔╝ \u001b[0m","\u001b[31m██╔══██╗██║   ██║██║╚██╗██║██╔══██║██║███╗██║██╔══██║  ╚██╔╝  \u001b[0m","\u001b[31m██║  ██║╚██████╔╝██║ ╚████║██║  ██║╚███╔███╔╝██║  ██║   ██║   \u001b[0m","\u001b[90m╚═╝  ╚═╝ ╚═════╝ ╚═╝  ╚═══╝╚═╝  ╚═╝ ╚══╝╚══╝ ╚═╝  ╚═╝   ╚═╝   \u001b[0m","\u001b[91m        ██████╗ ███████╗██╗   ██╗██╗██╗     \u001b[0m","\u001b[91m        ██╔══██╗██╔════╝██║   ██║██║██║     \u001b[0m","\u001b[31m        ██║  ██║█████╗  ██║   ██║██║██║     \u001b[0m","\u001b[31m        ██║  ██║██╔══╝  ╚██╗ ██╔╝██║██║     \u001b[0m","\u001b[31m        ██████╔╝███████╗ ╚████╔╝ ██║███████╗\u001b[0m","\u001b[90m        ╚═════╝ ╚══════╝  ╚═══╝  ╚═╝╚══════╝\u001b[0m"],"width":62,"height":12,"credit":"Pablo Murad (runawaydevil) · 2026","author":"Pablo Murad","license":"© 2026 Pablo Murad. Original work for runawaydevil.com; reuse by permission.","notes":"Block letterform on a five-row grid, shaded bright red to dark red. Drawn for this site; not derived from any artpack.","source":"original","year":2026,"sauce":null}],"tags":[{"tag":"runawaydevil","count":4},{"tag":"ansi","count":1},{"tag":"art","count":1},{"tag":"bbs","count":1},{"tag":"design","count":1},{"tag":"memoria","count":1},{"tag":"portugues","count":1},{"tag":"privacy","count":1},{"tag":"security","count":1},{"tag":"software","count":1},{"tag":"terminal","count":1},{"tag":"web","count":1}]}