From ecb899a8667931d2141563a1d7210e476a8dacff Mon Sep 17 00:00:00 2001 From: 0xA0 Date: Tue, 13 Oct 2026 14:31:03 +0200 Subject: [PATCH] parse art once per request; inline the art font and set a dark color-scheme to stop the load flicker --- css/style.css | 6 +++-- inc/art.php | 62 ++++++++++++++++++++++++++++++++------------------ inc/header.php | 2 +- 3 files changed, 45 insertions(+), 25 deletions(-) diff --git a/css/style.css b/css/style.css index 6e34da7..b0e1183 100644 --- a/css/style.css +++ b/css/style.css @@ -194,10 +194,12 @@ button { /* The art is drawn for .6em-wide characters; system monospaces range from .5em up, which squashes it. So it gets its own: JetBrains Mono's printable - ASCII (OFL), about 4.5 KB. */ + ASCII (OFL), 4.6 KB, inlined so it arrives with this file -- as a separate + download the art stayed invisible until it landed, then popped in. + Source: css/sideart.woff2. */ @font-face { font-family: sideart; - src: url(sideart.woff2) format("woff2"); + src: url(data:font/woff2;base64,d09GMgABAAAAABHoAA4AAAAAJPgAABGTAAJN0gAAAAAAAAAAAAAAAAAAAAAAAAAAGhwbIBwqBmAANBEICrcYqXsBNgIkA4FKC4FKAAQgBYo8ByAbnByzolZtVlaK/zKBjuthC0HK0BJYlHMnJkkaS5N8Zf1ZvOdasvnno2UPs2awLJ26b4Qks/AkY/t3h7hkPJqXZCETNRRaZ6yTWdcvpTI8busfYHFZFgacTYkMBmMjJpJzgOAMaoBgUcoZF9lcJnc/suVH19XaoJI6JTGESk5mL1Vcgd07nv/vYve/GVqClZXV1T1pHmVZRHEIGXa8dMZkNpcE/Hgx6zp1+/22R9yO9sUUC0mvu+UMoUNJ5v87fb1Pdta7FkwADAzAJUDD38oetN0y9afZk7escB2e9X+6Wjtzh+yiu+CdBumg6LQrqKl+fkJ25v7J2uOtnHkwC5rLIqadaE01eA7RUkQrqKioeZTQ8RiutBcJEor3uegmIrItlGBtKti1YRDg5EgtLzIPxtDTgUGAXVIJxmVssoMCRsB3AgAAGlhWhXn6ac9lcgAsgFqOyhiX7JALfdFaAIvgJnJHVChAYQO2u/KdnwDIB8ey9s/4AHhi1QH76posZbng2mQAwUAShnNkuZEL7XYVzc6t7EOM1aR0aLpNm2z4G8AJsB5B+6+o0spH6idnzmRgoiRb2eCSBqSOmZlBkcHO4bbVsRL4BybIauM4VC+UdZJagSZ0tEkFQppmd5QkPyio+AQ4W/euK5wenMMQwRb1FJU5Ligda905k8SkNkZeZMkX+FudIehHJX6sglGkZgKdFR2Yt9Jpj9xjD9Dobya7s1/vG2anszMg9xf/NCddF7E2JvZFyAEyquQd6TpCAxpl0lVz8kZmzN9BMd7Cxz50LsQrTrX99SDxfpBhgumMXfm5bP//x1ohvbHUEVeuKfXRLrey2OBq6sdM2gi7AJDDa27DOKw8K8KH4RTXKphYEWxwcEDA2fLWRzqv4cPvydQuhsKWhfL33rCee8uHxh/4AHLlwMv7o3dAz87kdTGYe+fxJE/vqyBEf9f6xxwTIjPSQx9G724L7ymVvQvHmXKYSFFUKASh8Il1Eos54UHiYeX0Cz+KNYQVLS/M6FlSti/BDipx3nlB+Ia7cyE7JBxwElkSlPRMGTQD+o6AJGacxZwhZgQL+gupxEos3AGiTeHOcIWLoND8PrmwqDydYDDUNHM7sR496UU4YXump08vslFBxWJ1QmYu/zpF/zTZx8SRV6ssZ7HT2G6DM0QYd67qArbyXVQrL0YmfDX9O8DJxHpnXhYwe2twe6Y/R7KcRSJ2nA+2uG8F8zLHIa8GJRZHm+lDh/XQQhaEOqRLDa1MUchpiGj6uRHOmNIMpzRouTNcE2EXLA58jUEC/fInYrN+cdl1q7xE8fEXwSljFtMUyjAwFNqrsqiAmHGNLB0amh0BcX7DgqUWavQHVSZ1dysfbEUleLrEuqdN1pWgRbyX2Btb+nfrrv7HMI9PHROlx/j00biH9np4FhkNqKY4/qGhOyLjlsY+RJ/GW4qUro3j01ASTCfCf01Ou366dVgMkEe3c9Zp9q5QPqFu4iTXYepsI3Ome4eTOn1OlEPQJpka+iORP5DVKB5FOJaEXUFyLLqh3tEr9FFTtaFsXLDnuGrhhwgdJOY+85MXDqlwMCX2tDl+C7tPqfZi4bvdCx51m27Qrq5tVPQyLZDHCJI4Dv+Eo9ABwvHmhuN/9jOepuKjidbgVK89Ar8HuXwBwGlsBmG5SLwlIfqRH2u/gGoGbiZWNp0RgIUKgyjBslUl1PiKGzk86iXgOnpCXTczazuoV0U3R7RkbHwyRZpRWFNwqtgyh0QMOMCLmfqj3liTmbSy0MVfvec2jxMhquVe2LQtCJf6KI56RGHmPJSHwNyLN0Cl9SqZR/L2zNpDePw4DWfr8YTaDYm9vwDmYa1XgMyRjbNTxOuKXUM/A2dskDdxKO+IqREHQqg55rozUh3Yu3V6iIH9+ziunPNxWqymvBVafBNq5vEmevxqYye8qVU56P4KJq9oudORRx0uGJTyeWcjXNPRP+LGi4kNYYnLMA0GdukH48OYeokPLNbBEG9N95aepgIiKtY2hJcjiopDHTl8PdCv3+PRxtspbHkE9Je+vWjmolRJ8QeNz6xFwMZ72Yn5yjJerI8KPFC67fkhJZyVXr7JNJi1eXVXfqSH4aCEV9ryzhnmEb0thC9ky1+jWE3m+0OgleJPeuKPcf08a7+B7Ol1Fqw+cLuejz3/6R25B6bkdwjqGtD7M+DAdtSgn4pNNejRLkMan93+44QBLIy9WlJRipVWlGQx7C2TQMbzjXlItUpbUp4HS4s/12nl4I2CRSDokhHtbiMrI8GlzW6nWUryG+wyl8hSx/2Wt/ixR/6mYvS2azvu83uWn1lIVnEJWVyMjR8v4UfEMvFFAMT82mBeLfgkta30yLOHbh2C0Kqc/3XbROPWWxbR0nbe/0A85USnddm4lOWWVYUAocQ55UmA60GtAO18+FFx5v15kcWFlt7FuSP9Lfjspfals9talobn9mqgO+a9nxG/dh3tDFftul54992LboD/u/MHiQcfdbVvF7x8vEnYpxOCzWUESoBaNvfZCu7z3MVZ8Sww9M8TX3ZnvTtNYDjpQ307Bbo+mMTja1SbFHqTElXqk2U74J8KpxYVTikEb71oKqDIJ+SP7w6dHh3bcJIEbbafWKNaZ41Ci8NKXI9QTF8q4Q8Mx3yNUELb/NjzJhJw2IGhqJdS6HQKv0Dk/Beslpv315G77Nfuac8LDXSIvMNRsCNt2Mq+HJz70OIfbq5OA+7kO6hXaHTyJ/1rSSHjgeFA/0CgPnpcdWw8Iod7dIIK+rPzDXQGKntIWIp5oCa/GocjPTBuxQiHTheLsN5jAw+w1s8bpp56yc5YC2rZurBE7lHpLH5sEmuLxiWXSdo8AhXqFrioTC5WbmFhkwxRncrYSo9Edw3SYBKJBoOk2gho6KZu1GpQKCXqA0rFbwKPZqCy4yRH1Q1J/OpmOByGdc3hTa62e0cL1B6u3l9evr+6vXHVmaE0KDv4VnEucuZUnSZ7ZGptZKJX3qBB1CmZam1KbGVRgeQAsB8krhJXvj/LgB39HCjOl5T9LwPPv3FRhBpwGDbg2FLHUjzYCLnVMj5ll8Tdimsc02bGPhvtKpd9/fZDy+KrVsWnWxyUoaXN2Zw06Ai02r2ZiTvs7QTlT/TY6Ui8a8BYXs/bUTtUd4zHO16XrN0BRPdPjNw30cF/+F2gwKbeJf3g/5mOoqL82v4DXdkX6ai/HcLbN7FOo8JKU0N3vI9ee3JYrlZKJJhSnoLGVXq1XKE3qMaBmE0nBn0GXxa0byXqpRBUDcNqNWJA1C1Do5RX9QGd2fubgxLIq6nQQD5/lmaf1a82tKgrMPgv+wGdPU0aP6oiqnKMJHBnr5JtPylIuRbwVwhhiJFrZtQg9B7yuztWmbYrS0FfQRQ+H/EVAcLk3qa95DpLDaayw6mhIKXB9ShaPidNAVUQXDIEOrOUvO+EzHfL/cBI6AIRbgWrstQGAwn02bWdsAdem3W2C5nLcleKh5ZYooP5LbnX/O2Ayi55L7ogR88kKg85hb7vRuezVNxDoJbti8UiTZFd+tpgWFrtJWCECobE5Soboh4qvw4uSQ4C+kOCO+uW7oRxvRLBm+Gd4EwmeDFnR+5ZV2lkfn3yoQ//hPGWDOaYHv5IIAGkrwF0ZT/EvI2SLpXBGP5kD+LfJhlpWr8B0uByWKODNgA+O7J00EcpzVoUaWmW1zWg9w6UaGdbgrhc7OwSKJHus/NYKZEGkUgwRFR1PhIr1c8mBkyekRj4f0NDfkU+tQHUsoO9Ue8sapZCrktpWKdgHqMvMgi+u3eic/yHR2c4m5pmuR5Nfd913wTobZuwQYUCFp4zJw8XNBd6sdovH58zJ+f1ywutrokYxhWy0JkzWSiGc0HVy7EnQj3RN1fwKANvRfxN/xPel1Oh8c9X8IxG7oqlX4D/Z779lGdr7V2vzJ//0V2X6NqdM4GD0tdryePHTWwkeK7/8q/E4SmHCXDlrkzs7R//gX+9DFbbzbX262tbBxmJx6plr62OMwDsN35gBH+zp/5us7je2FojuVjbO9T460wqBx2wd9IRd9toO82MO+QCo9qskprTdXRdw2PAyEasigAjTGWpNGOckSSUiM3b7z7R7e2PmmV/a9Lav2RSVIfsQ1DdhWCp0D5fSulOBphOqhKDIBjPT3k/gmtgCJNVUExnMgAms0lff/cJt7ffhhDKJIMxnqayVJgRsCpKzZAKE6AvJPtLm9b8DWrZ3uFkaFXIfe1ezCOWdKE+V6NSh8tFKM9DFWgTxBtGUISpcaVSjWPgzY1pMp3AWg36E+QJVJ74kPxwI0hnyMN8e72I0c6Kmhthh7e3O9nt7XfAZmmU6W7n2YKe9hmVy+eHGxEMWgp5vCpfajEjaIRVNm/AFaf8CKJuglAd2g28WWp/+OBHoeu595SGj//S+ysYb2mz498+QC5QlbdxLpJAwbO0MLVTeZULTzH8bJ4EsahO2k+V/VZU+GvZaUMbLCw6HHTyqPBCXX0qZW2+P5noWRZOxGkaqcPLepIJnxVWY+guNGdgucYFXwY6MvRNQmzePjexfa1KmrXZ1O9ySp3IpLtlLNoqy3q3r4+U8kYv/y7+7/dP9CL6J4jyqDCTNirRts6gm0/xlwzBSg3aDebmd9KkieJRwjpRb1UzWrB/bkppUul9rc46graoOoq41XUNskp589bZm2fvMNP2TuDXtlrUmmPkscPU4Q/JDzVFzMI0CWrY7ljU2011K1S4FEJxBYNPmvLHe3xvKpb+R4C+s+MSbskXjLRzLdLVXDuYoZVoV1PgqUhoMMPOla02dHH9yBg8MAMsM2JjQPTIojsmRfouRJ6WLn7aUd8x+eGFkaFM5Dk99M2oez3gRoZ4/TnWcaUt8Taf/7Z/ic09cwJQlHikjtxJyUZ0JHi23xVqy7NMO6VdhiWIBLZMuwILyrSueqKeL5cfjQYMN+S9KczcIoAo+KoT/Nd/mboMuvvHlJHIglMbZJaqxTLdBgqSPjRtPezi1bUkvSN5FVtKLEUfWFKJOoN2KzDQ/YfDzbicu3De2Yk5fCnu2CvwZjh+fcBBPrrqGsl+7W4StGfIcUSrUd7OIbvzvalYAKjXxDfT/ZtB7Yay1uISsqyMLCluBTN4oGAfZnkscs78xYFVXAD/yiPpvk6orVeeP0lVRndPb/VcV+vpZJ3M2koaZnIAujV26DtN9ej7E6DXO/vWC9xB8S8obV8A8PwGRwCA/zff/VP2XzT/dg8AMwUg+Ccb1Zsl8H8yc3f1riUuKIC37SroTRPzJ1dCJcwrtsgyIlBMcDgjFEkNNeF5Dn/EVeqJdOlsorm5BBnuyM5+fz2KSxIeopTWexeZo0oFh+cZ7UNQOEmjk4uTnZQJ0Uf0KqHQyf7/c4py4KM0KSXShGL0Pzecg2MYxlYMsFlMrLFeHr9kGM14XPqxzq49r4Am8u7TlNtDM2j7RjOqVY1msq1OzaxSr6Dyi5i2ZuUPrdnMM6w51PLQyE7NVeX5SXNr9Tn35gV1AxqDokbEhYX0SOIQEbTSHHaZB6ThTCeprcN6hQ1IpM0GKR9ME+LFjgjwo6QxQ172dCgOpyp0qOePSpDj4wsJw8Dc5NPAX/fQj79W7i/W6mtbDWjisMAlSdr6hwzpW9c40+o44a+aQ6yBRAMBRrZNgAYKg5HccAOx3QjGoSUb5CUMCvW4POmsMkyoAYR447BS9SRoiHY1mAVb0pHwHPz6SOzWDEZTvywKpvAoBM5BmE0L6GdO9+L4tQmaCKBwgzgbzAUigmvZF7JRURKzQ+L7wi/hAXVk3ImdkOTlFb9CITVUBsrRew0aNi9K1dkGkZVH9lnGRvYdrJwgMc1hozxIKf5KAtzjnrJ+ARQKEC+HCtFqII4ZUwBjwRYmHFZRhhb4q5pwWJ23aTAhtswrl3N1typomCdUXtan73tR6jqZirJxmNYR6DC5N0V2gl+cekrSQglMariuMmQ6BVY4E+vSXxJvw3UbWoKlGr/vgwhAAAAA) format("woff2"); } .side { diff --git a/inc/art.php b/inc/art.php index 08e5f99..80fd093 100644 --- a/inc/art.php +++ b/inc/art.php @@ -69,6 +69,43 @@ function ansi_html(string $file, bool $mirror = false): string return ansi_html_cells($raw, $mirror); } +/** + * Plain-ASCII art as [lines of chars, lines of color-index bytes, color => index], + * or null with too many colors for a byte. One regex pass over the whole file; + * kept per request so the mirrored side doesn't parse it a second time. + */ +function ansi_parse_bytes(string $raw): ?array +{ + static $memo = [], $done = []; + $key = md5($raw); + if (array_key_exists($key, $done)) { + return $done[$key]; + } + $index = ['' => 0]; // color hex => byte (0 = uncolored) + $chars = []; + $masks = []; + $c = $m = ''; + $cur = null; + foreach (preg_split('/(\e\[[0-9;]*m|\n)/', rtrim($raw, "\n"), -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY) as $part) { + if ($part === "\n") { // colors don't carry across lines + $chars[] = $c; + $masks[] = $m; + $c = $m = ''; + $cur = null; + } elseif ($part[0] === "\e") { + // a handful of distinct codes repeated thousands of times: decode each once + $cur = $memo[$part . '|' . $cur] ??= ansi_color(explode(';', substr($part, 2, -1)), $cur); + } else { + $k = $index[$cur ?? ''] ??= count($index); + $c .= $part; + $m .= str_repeat(chr($k), strlen($part)); + } + } + $chars[] = $c; + $masks[] = $m; + return $done[$key] = count($index) > 250 ? null : [$chars, $masks, $index]; +} + /** * Fast path for plain-ASCII art (one byte per cell): each line becomes a * string of characters plus a parallel string of color indices, and every @@ -81,30 +118,11 @@ function ansi_html_bytes(string $raw, bool $mirror): ?string $from ??= implode(array_map('chr', range(0, 255))); $to ??= "\xff" . str_repeat("\0", 255); - static $memo = []; - $index = ['' => 0]; // color hex => byte (0 = uncolored) - $chars = []; - $masks = []; - foreach (explode("\n", rtrim($raw, "\n")) as $line) { - $c = ''; - $m = ''; - $cur = null; - foreach (preg_split('/(\e\[[0-9;]*m)/', $line, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY) as $part) { - if ($part[0] === "\e") { - // a handful of distinct codes repeated thousands of times: decode each once - $cur = $memo[$part . '|' . $cur] ??= ansi_color(explode(';', substr($part, 2, -1)), $cur); - continue; - } - $k = $index[$cur ?? ''] ??= count($index); - $c .= $part; - $m .= str_repeat(chr($k), strlen($part)); - } - $chars[] = $c; - $masks[] = $m; - } - if (count($index) > 250) { + $parsed = ansi_parse_bytes($raw); + if ($parsed === null) { return null; } + [$chars, $masks, $index] = $parsed; $width = max(array_map('strlen', $chars) ?: [0]); foreach ($chars as $i => $c) { diff --git a/inc/header.php b/inc/header.php index 783a275..2d9adf8 100644 --- a/inc/header.php +++ b/inc/header.php @@ -6,10 +6,10 @@ require_once __DIR__ . '/../config.php'; + <?= htmlspecialchars($page_title ?? SITE_TITLE) ?> -