PageSourceSearch

https://innig.net/software/

html innig.net collected 2026-10-02 05:04:00 UTC 13,906 bytes, 275 lines download raw bytes

1<!DOCTYPE html>
2<html lang='en' style='--theme-hue: 276;'>
3<head>
4<meta charset='utf-8'>
5<!--[if IE]> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" /> <![endif]-->
6<title>Paul Software – innig.net</title>
7<!--
8Web search rests on a social contract: search engines may crawl and index our sites because
9and ONLY because they direct people to our sites.
10If Google is moving to LLM search results that do not direct traffic to individual sites,
11then they have broken that contract. Data from this site is therefore off limits to them.
12-->
13<meta content='noindex,nofollow' name='google'>
14<meta content='noindex,nofollow' name='Googlebot'>
15<meta content='noindex,nofollow' name='Googlebot-Extended'>
16<meta content='noindex,nofollow' name='Googlebot-Image'>
17<meta content='noindex,nofollow' name='Googlebot-News'>
18<meta content='noindex,nofollow' name='Googlebot-Video'>
19<meta content='noindex,nofollow' name='Storebot-Google'>
20<meta content='noindex,nofollow' name='GoogleOther'>
21<meta content='noindex,nofollow' name='GoogleOther-Image'>
22<meta content='noindex,nofollow' name='GoogleOther-Video'>
23<meta content='noindex,nofollow' name='Google-CloudVertexBot'>
24<meta content='noindex,nofollow' name='Google-Extended'>
25<meta content='width=device-width,initial-scale=1' name='viewport'>
26<link href='/style/site.css?3scgfnlv' media='all' rel='stylesheet' type='text/css'>
27<link href='/favicon/favicon-16x16.png' rel='icon' sizes='16x16' type='image/png'>
28<link href='/favicon/favicon-32x32.png' rel='icon' sizes='32x32' type='image/png'>
29<link href='/favicon/apple-touch-icon.png' rel='apple-touch-icon' sizes='180x180'>
30<link href='/favicon/site.webmanifest' rel='manifest'>
31<link href='https://innig.net/everything.rss' rel='alternate' title='All of innig.net' type='application/rss+xml'>
32<script async src='/script/audio-player.js?3scgfnlv' type='module'></script>
vendor: 1 bytes, line 32
32
33<script async src='/script/album-parallax.js?3scgfnlv' type='module'></script>
vendor: 1 bytes, line 33
33
34<script defer src='/script/telemetry.js' type='module'></script>
34
35</head>
36<body><div class="sfuYldIV_v">
37<nav class='nav-top'>
38<div class='nav-bar'>
39<div class='nav-items breadcrumbs'>
40<div class='nav-item'>
41<a class='' href='/'>innig.net</a>
42</div>
43<div class='nav-item'>
44<a class='current-page' href='#'>Paul Software</a>
45</div>
46</div>
47<div class='nav-items meta'>
48<div class='nav-item'>
49<button class='dropdown-control'>
50<div class="sfpADGMsz9"><div class='svg-icon'><svg class="svg-rss" version="1.1" viewBox="0 0 144 144" x="0px" xmlns="http://www.w3.org/2000/svg" y="0px"><style type="text/css"> .svg-rss .st0 { stroke-width:32; stroke-miterlimit:10; } </style><circle class="fill-fg" cx="17.3" cy="126.7" r="17.3"/><path class="st0 stroke-fg" d="M76.8,144c0-42.4-34.4-76.8-76.8-76.8"/><path class="st0 stroke-fg" d="M128,144C128,73.3,70.7,16,0,16"/></svg> </div>
51</div>
52<div class='screen-reader-text'>Show/hide RSS feeds</div>
53</button>
54<div class='dropdown'>
55<div class='dropdown-content'>
56<label>RSS Feeds</label>
57<ul>
58<li>
59<a href='/everything.rss'>
60<div class='inline-icon'><div class="sfpADGMsz9"><div class='svg-icon'><svg class="svg-rss" version="1.1" viewBox="0 0 144 144" x="0px" xmlns="http://www.w3.org/2000/svg" y="0px"><style type="text/css"> .svg-rss .st0 { stroke-width:32; stroke-miterlimit:10; } </style><circle class="fill-fg" cx="17.3" cy="126.7" r="17.3"/><path class="st0 stroke-fg" d="M76.8,144c0-42.4-34.4-76.8-76.8-76.8"/><path class="st0 stroke-fg" d="M128,144C128,73.3,70.7,16,0,16"/></svg> </div>
61</div></div>
62<div class='caption'>All of innig.net
63<div class='screen-reader-text'>RSS</div>
64</div>
65</a>
66</li>
67</ul>
68
69</div>
70</div>
71</div>
72<div class='nav-item'>
73<button class='dropdown-control'>
74<div class="sfpADGMsz9"><div class='svg-icon'><svg viewBox="0 0 74 79" xmlns="http://www.w3.org/2000/svg"><path class="fill-fg" d="M73.7 17.4C72.5 9.02 65.1 2.4 56.424 1.1C54.9 0.943 49.3 0.118 36.3 0.118H36.2C23.3 0.118 20.5 0.943 19.0 1.1C10.56 2.45 2.77 8.34 0.906 16.826C-0.07854 21.0 -0.161 25.6 0.012 29.8C0.375 35.9 0.374 42.0 0.96 48.1C1.34 52.1 1.92 56.1 2.95 60.0C4.71 67.3 11.9 73.3 19.0 75.86C26.6 78.4 34.8 78.8 42.724 77.0C43.5 76.8 44.4 76.6 45.2 76.4C47.1 75.8 49.4 75.1 51.0 73.9C51.0 73.9 51.1 73.9 51.1 73.8C51.1 73.8 51.1 73.8 51.1 73.8V67.9C51.1 67.9 51.1 67.8 51.1 67.862C51.1 67.8 51.0 67.8 51.0 67.8C51.0 67.7 51.0 67.7 50.9 67.7C50.9 67.764 50.9 67.7 50.9 67.7C45.8 68.9 40.7 69.5 35.5 69.5C26.694 69.5 24.3 65.3 23.6 63.6C23.0 62.1 22.7 60.5 22.5 58.9C22.5 58.9 22.5 58.921 22.5 58.8C22.6 58.8 22.621 58.8 22.6 58.8C22.6 58.8 22.6 58.8 22.714 58.7C22.7 58.7 22.7 58.794 22.794 58.8C27.7 59.9 32.799 60.5 37.8 60.5C39.1 60.5 40.3 60.5 41.5 60.5C46.6 60.3 52.0 60.
741 57.0 59.1C57.1 59.1 57.3 59.1 57.4 59.0C65.3 57.5 72.9 52.8 73.6 40.8C73.7 40.4 73.7 35.9 73.7 35.4C73.7 33.7 74.3 23.6 73.7 17.4ZM61.4 47.3H53.1V27.107C53.1 22.8 51.3 20.6 47.7 20.6C43.7 20.6 41.6 23.2 41.6 28.3V39.3H33.4V28.3C33.4 23.2 31.3 20.6 27.3 20.6C23.7 20.6 21.9 22.8 21.9 27.107V47.3H13.6V26.4C13.6 22.2 14.7 18.8 16.9 16.3C19.1 13.8 22.1 12.5 25.8 12.5C30.1 12.5 33.3 14.1 35.4 17.4L37.5 20.8L39.6 17.4C41.7 14.1 45.0 12.5 49.2 12.5C52.9 12.5 55.9 13.8 58.2 16.3C60.4 18.8 61.4 22.2 61.4 26.4L61.4 47.3Z"/></svg> </div>
75</div>
76<div class='screen-reader-text'>Show/hide Paul’s social media info</div>
77</button>
78<div class='dropdown'>
79<div class='dropdown-content'>
80<label>Social media</label>
81<ul>
82<li>
83<div class="sffbaIn90o"><a href='https://hachyderm.io/@inthehands' rel='me'>
84<div class='inline-icon'><div class="sfpADGMsz9"><div class='svg-icon'><svg viewBox="0 0 74 79" xmlns="http://www.w3.org/2000/svg"><path class="fill-fg" d="M73.7 17.4C72.5 9.02 65.1 2.4 56.424 1.1C54.9 0.943 49.3 0.118 36.3 0.118H36.2C23.3 0.118 20.5 0.943 19.0 1.1C10.56 2.45 2.77 8.34 0.906 16.826C-0.07854 21.0 -0.161 25.6 0.012 29.8C0.375 35.9 0.374 42.0 0.96 48.1C1.34 52.1 1.92 56.1 2.95 60.0C4.71 67.3 11.9 73.3 19.0 75.86C26.6 78.4 34.8 78.8 42.724 77.0C43.5 76.8 44.4 76.6 45.2 76.4C47.1 75.8 49.4 75.1 51.0 73.9C51.0 73.9 51.1 73.9 51.1 73.8C51.1 73.8 51.1 73.8 51.1 73.8V67.9C51.1 67.9 51.1 67.8 51.1 67.862C51.1 67.8 51.0 67.8 51.0 67.8C51.0 67.7 51.0 67.7 50.9 67.7C50.9 67.764 50.9 67.7 50.9 67.7C45.8 68.9 40.7 69.5 35.5 69.5C26.694 69.5 24.3 65.3 23.6 63.6C23.0 62.1 22.7 60.5 22.5 58.9C22.5 58.9 22.5 58.921 22.5 58.8C22.6 58.8 22.621 58.8 22.6 58.8C22.6 58.8 22.6 58.8 22.714 58.7C22.7 58.7 22.7 58.794 22.794 58.8C27.7 59.9 32.799 60.5 37.8 60.5C39.1 60.5 40.3 60.5 41.5 60.5C46.6 60.3 52.0 60.
841 57.0 59.1C57.1 59.1 57.3 59.1 57.4 59.0C65.3 57.5 72.9 52.8 73.6 40.8C73.7 40.4 73.7 35.9 73.7 35.4C73.7 33.7 74.3 23.6 73.7 17.4ZM61.4 47.3H53.1V27.107C53.1 22.8 51.3 20.6 47.7 20.6C43.7 20.6 41.6 23.2 41.6 28.3V39.3H33.4V28.3C33.4 23.2 31.3 20.6 27.3 20.6C23.7 20.6 21.9 22.8 21.9 27.107V47.3H13.6V26.4C13.6 22.2 14.7 18.8 16.9 16.3C19.1 13.8 22.1 12.5 25.8 12.5C30.1 12.5 33.3 14.1 35.4 17.4L37.5 20.8L39.6 17.4C41.7 14.1 45.0 12.5 49.2 12.5C52.9 12.5 55.9 13.8 58.2 16.3C60.4 18.8 61.4 22.2 61.4 26.4L61.4 47.3Z"/></svg> </div>
85</div></div>
86<div class='caption'>@[email protected]
87</div>
88</a>
89</div></li>
90<li class='fine-print'>
91You might find me on other social networks. Please be aware I am barely active on them.
92</li>
93</ul>
94</div>
95</div>
96</div>
97<div class='nav-item'>
98<button class='dropdown-control'>
99<div class="sfpADGMsz9"><div class='svg-icon'><svg version="1.1" viewBox="-1 -1 44 34" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" class="svg-email"><desc id="svgDescription">Contact</desc><defs><clipPath id="clip"><use xlink:href="#envelope-outline" /></clipPath></defs><path class="fill-fg" id="envelope-outline" d="M37,0H5C2.2,0,0,2.2,0,5v22c0,2.8,2.2,5,5,5h32c2.8,0,5-2.2,5-5V5C42,2.2,39.8,0,37,0z" /><path class="stroke-thin stroke-bg" style="clip-path:url(#clip);" d="M29,16L42.4,29.4 M13,16L-0.4,29.4 M42.4,2.6L23.8,21.2c-1.6,1.6-4.1,1.6-5.7,0L-0.4,2.6" /></svg> </div>
100</div>
101<div class='screen-reader-text'>Show/hide Paul’s contact info</div>
102</button>
103<div class='dropdown'>
104<div class='dropdown-content'>
105<label>Contact Paul</label>
106<ul>
107<li>
108<a href='mailto:[email protected]'>
109<div class='inline-icon'><div class="sfpADGMsz9"><div class='svg-icon'><svg version="1.1" viewBox="-1 -1 44 34" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" class="svg-email"><desc id="svgDescription">Contact</desc><defs><clipPath id="clip"><use xlink:href="#envelope-outline" /></clipPath></defs><path class="fill-fg" id="envelope-outline" d="M37,0H5C2.2,0,0,2.2,0,5v22c0,2.8,2.2,5,5,5h32c2.8,0,5-2.2,5-5V5C42,2.2,39.8,0,37,0z" /><path class="stroke-thin stroke-bg" style="clip-path:url(#clip);" d="M29,16L42.4,29.4 M13,16L-0.4,29.4 M42.4,2.6L23.8,21.2c-1.6,1.6-4.1,1.6-5.7,0L-0.4,2.6" /></svg> </div>
110</div></div>
111<div class='caption'>Email</div>
112</a>
112<script>
113  (() => {
114    const link = document.currentScript.previousElementSibling;
115  
116    function parseBase36(str) {
117      let result = 0n;
118      for (const c of str) {
119        let digit = BigInt(c.charCodeAt(0)) - 48n;
120        if (digit >= 10n) {
121          digit -= 39n;
122        }
123        result = result * 36n + digit;
124      }
125      return result;
126    }
127  
128    function unwind(str) {
129      return (parseBase36(str) * 471484391066497139462578578088n % 571241339539932666723316134947n)
130        .toString(36);
131    }
132  
133    const timer = Date.now();
134    let iters = 32;
135    function unscramble() {
136      iters--;
137      const parts = link.href.split(/[^\w]/)
138      link.href = parts[0] + ":" + unwind(parts[1]) + "@" + unwind(parts[2]) + "." + parts[3];
139      if (iters > 0) {
140        setTimeout(unscramble, 1);
141      }
142    }
143    unscramble();
144  })();
145</script>
145
146</li>
147</ul>
148</div>
149</div>
150</div>
151</div>
152</div>
153<script type='module'>
154  let visibleDropdown = null;
155  
156  document.addEventListener("click", (e) => {
157    for (let elem = e.target; elem; elem = elem.parentElement) {
158      if (elem.classList.contains("dropdown-control")) {
159        toggleDropdown(findAssociatedDropdown(elem));
160        return;
161      } else if(elem.classList.contains("dropdown")) {
162        return;  // click inside dropdown: leave it visible
163      }
164    }
165    hideDropdown(visibleDropdown);  // click outside dropdown: hide it
166  });
167  
168  function findAssociatedDropdown(elem) {
169    return elem.parentElement.querySelector('.dropdown');
170  }
171  
172  function findAssociatedControl(elem) {
173    return elem.parentElement.querySelector('.dropdown-control');
174  }
175  
176  function toggleDropdown(elem) {
177    if (elem === visibleDropdown) {
178      hideDropdown(elem);
179    } else {
180      showDropdown(elem);
181    }
182  }
183  
184  function showDropdown(elem) {
185    if (visibleDropdown === elem) {
186      return;
187    }
188    if (visibleDropdown) {
189      hideDropdown(visibleDropdown);
190    }
191    if (!elem) {
192      return;
193    }
194  
195    visibleDropdown = elem;
196    elem.style.visibility = "visible";
197    const control = findAssociatedControl(elem);
198    control.classList.add("active");
199    elem.style.setProperty("--dropdown-control-width", control.clientWidth + "px");
200  }
201  
202  function hideDropdown(elem) {
203    if (elem) {
204      if (visibleDropdown === elem) {
205        visibleDropdown = null;
206      }
207      elem.style.visibility = "hidden";
208      findAssociatedControl(elem).classList.remove("active");
209    }
210  }
211</script>
vendor: 1 bytes, line 211
211
212<script type='module'>
213  import * as events from "/script/events.js";
214  
215  let navTop = document.querySelector(".nav-top");
216  let fadeInDistance = 300;
217  events.onLoadScrollAndResize(navTop, () => {
218    let opacity = Math.max(0, Math.min(1, window.scrollY / fadeInDistance)) * 100;
219    navTop.style.setProperty("--nav-border-fade", opacity + "%");
220  });
221</script>
221
222</nav>
223
224<main class=''>
225
226<div class='layout hero-heading centered-content'>
227<h1>Paul Software</h1>
228<div class='prose'>
229<p>I fell in love with programming at first sight in 1982 on an Apple ][+, and never looked back. Since then, I’ve done a wide variety of different kinds of programming: front end, back end, side end, runt end, mobile, desktop, web, data plumbing, scientific computing, algorithmic art, and assorted shenanigans. My projects don’t always involve cool fractals, but sometimes <a href="http://itunes.apple.com/us/app/make-a-bonsai/id696977286">they do</a>.</p>
230
231<p>From industry I wandered sideways into <a href="/teaching">teaching at Macalester College</a>. These days I juggle my time, sometimes successfully, between building software and helping students learn to build it.</p>
232
233<p>This software site, recently a wasteland of dead links, is undergoing a much-needed revamp. When it returns, I hope for it to include a place for a blog about software development, as well as links to cutting-edge, world-changing projects such as <a href="https://wookieepedia.org/">this one</a>.</p>
234
235</div>
236</div>
237
238</main>
239<div class="sf7gqyDS5t"><footer class='nav-bottom'>
240<div class='left'>
241<div class='feeds'>
242<label>RSS</label>
243<ul>
244<li>
245<a href='/everything.rss'>
246<div class='inline-icon'><div class="sfpADGMsz9"><div class='svg-icon'><svg class="svg-rss" version="1.1" viewBox="0 0 144 144" x="0px" xmlns="http://www.w3.org/2000/svg" y="0px"><style type="text/css"> .svg-rss .st0 { stroke-width:32; stroke-miterlimit:10; } </style><circle class="fill-fg" cx="17.3" cy="126.7" r="17.3"/><path class="st0 stroke-fg" d="M76.8,144c0-42.4-34.4-76.8-76.8-76.8"/><path class="st0 stroke-fg" d="M128,144C128,73.3,70.7,16,0,16"/></svg> </div>
247</div></div>
248<div class='caption'>All of innig.net
249<div class='screen-reader-text'>RSS</div>
250</div>
251</a>
252</li>
253</ul>
254
255</div>
256<div class='empty'></div>
257</div>
258<div class='center'>
259<a class='imglink' href='/'>
260<div class='innig-logo-mini'></div>
261<div class='screen-reader-text'>innig.net home</div>
262</a>
263</div>
264<div class='right'>
265<div class='empty'></div>
266
267<div class='copyright'>
268<a href='/copyright'>© 2026 Paul Cantrell</a>
269</div>
270</div>
271</footer>
272</div>
273
274</div></body>
275</html>

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.