Oh no! Oh no. 😿
Oh no! Oh no. 😿
I just posted Sticky Scribbles to Glitch as a Glitch Jam entry for August 2024. (Update 2025-05-23: Glitch is going away. You can find Sticky Scribbles at its original location.) The prompt got me thinking about some old web art projects that have come and gone, so I picked one up, dusted it off, dropped it on the floor, stepped on it, cut myself on the pieces, bought expensive raw goods from the store, and rebuilt a nearly unrecognizable new version that does the same thing.
I thought I’d post the write-up on my blog, as well.
Need to erase? A page refresh will clear all scribbling.
The fonts are vector strokes suitable for plotting. They’re called Hershey fonts and their story is pretty interesting!
You can also find the full source and history on GitHub.
Inspired by the August 2024 Glitch Jams prompt of “#justdraw”, I remembered an old project from 2011.
Back then I worked for MakerBot Industries and, at the urging of my friend Matt Griffin,
had started a descent into madnes- pen plotting. For me, that meant using the
MakerBot Unicorn a tool for the MakerBot Cupcake CNC that replaced the plastic extruder
to turn a 3D printer into a loosey-goosey pen plotter.
As with most DIY 3D printers of the day, the Cupcake CNC was driven by G-Code - short instructions sent over a serial port to tell it how to move its motors, etc. When I started playing around, the process for going from some vector artwork to G-Code was pretty labor intensive and required multiple tools.
The work area for the Cupcake CNC is 10cm square, which is just a little bit bigger than
a pad of 3"x3" name brand sticky notes. So, these easily becamie a target for my madnes making.
In February of that year I particpated in “Thing-a-Day” on Posterous (RIP), and worked on several projects to try and boost the ease of use of this whole ecosystem.
Mashing up some example Python code from the Unicorn release, and the Inkscape driver for Evil Mad Scientist Laboratories’ EggBot, this extension let you save (simple) drawings as G-Code that you could plot using a Cupcake CNC + Unicorn.
While I made this Inkscape extension to serve a very niche machine, I ended up continuing to improve it here and there. I was surprised to see it adopted by some DIY CNC projects from all over the world!
Unfortunately, I never made much of a habit out of using my own extension, so when the extension interfaces changed for Inkscape 0.91.x, I ended up marking it read-only.
There are over 100 forks, though, so maybe somebody picked it back up!
Matt introduced me to Hershey fonts, a set of vector fonts designed for the U.S. government for use on CNC machines for plotting or engraving. These were in a somewhat legible format, so I became a little obsessed with using them for plotter projects.
I’d love to share gratuitous details about rewriting bits of this little tool. Unfortunately, I worked on it live on my site that entire time without any version control. 😭
(Where was Glitch in 2011?? lol) (Update 2025-05-23: where will Glitch be in 2026 lolsob)
While you can find the entire history on the sticky-scribbles
GitHub,
I had a good time figuring out how I left this ~13 year old project and choosing ways to “modernize”
or at least “make it less bad to my eyes”.
The drawing canvas markup can be summarized like this:
<svg>
with background image of the stick note
<g>
roup with a painstakingly trial-and-errored transform
that made rendered Hershey
text look “mostly right”
<g>
roup for the actual rendered Hershey text<g>
roup for the scribblesWhen we scribble onto the canvas, the pointer coordinates don’t account for that transform
, so
if we save them as-is, they’ll be skewed from where they appear on the sticky note preview.
Previously, I tried to do a bunch of math on my own, which worked out really badly.
Since then, I realized that if I have an existing transform
, I can:
When we add the transformed coordinate to our scribble, it now visually lines up with the preview on the sticky note! Miracle.
The 2011 version of this demo used Jeff Wood’s jQuery SVG plugin
to rebuild pretty much the entire contents of the <svg>
any time something changed.
However, the main structure of the SVG described above doesn’t change at all! So I moved the
<g>
roups that contain the transform
to make things line up with the sticky note, and its
inner <g>
roups for the rendered Hershey text and pointer scribbles into the markup on the page.
There were still a couple of useful utilities for creating <g>
roup and <path>
elements in the
jQuery SVG plugin, so I made my own version in a little ES module svg
helper
There were lots of jQuery-isms that I vanilla-ized and in many cases modernized.
$('#someid')
with a single document.querySelector
and a variable$(someArray).each(...)
with for (const item of someArray)
hershey.js
font parsing and rendering helper as an ES module.
async
and Promise
s to get rid of some messy setTimeout
calls around font loading.var
declarations with let
and const
One of the biggest changes was extracting the handling of events and (re-)rendering out of a big spaghetti ball and into self-contained bits. I did these as web components that don’t actually manage any child HTML. Instead, their attributes tell them which elements on the page they should hook into for events or render onto.
The <svg-scribbler>
component is interesting because I have it lean more into using the DOM as
new scribbles are added.
Previously: as the user draws a new scribble, they’re pushed into an array, and on every change we basically call a “render” function that throws out the SVG contents and re-creates it.
The new component simply creates a new <path>
element when the user starts drawing and updates the
d
attribute as the user draws. When they stop drawing, the <path>
is already in the page and done.
When the user starts drawing again, we a new <path>
element is created without disturbing any
existing <path>
s from previous scribbles.
Take a look at js/svg-scribbler.js
for details.
Similarly, the <svg-hersheytext>
component takes care of listening for changes as you type
in the <textarea>
and re-rendering the <path>
s for the text contents.
One wrinkle here is that we have a <select>
dropdown to let you select a different Hershey
font. Previously, a jQuery change
handler on the <select>
for choosing a Hershey font would load
the newly selected font and then call what amounted to a top-level “render” function to draw
all Hershey text and scribbles again.
The updated version uses a simpler event handler that emits a custom HersheyFont:updated
event.
<svg-hersheytext>
elements listen for that custom events on the window
object, and re-renders.
If you haven’t used them, I think Chris Ferdinandi does a great job explaining the how and why of custom events.
Wrapped up in the previous spaghetti of “render-everything” style calls was a stop to generate
an SVG string and slap it in a <textarea>
.
Now that the most of the SVG stays around in the page, I’ve replaced that with a MutationObserver that kicks off whenever elements are added or changed down in the SVG tree. I love this!
pointer*
events handling does unexpected things on multi-touch devicesFor example, this two-finger drag makes kind of a zig-zaggy filled-in area instead of two distinct lines:
I think this is fun and weird, actually.
inkscape-unicorn
is deprecated!Yeah so the tool this was designed to make drawings for isn’t really a going thing in 2024.
Um. Sorry? Enjoy your SVG scribbles anyway!
Tiny notes as I work on migrating my IndieWeb webring 🕸️💍 to a new tech stack.
Downloading the database from the glitch.com project’s .data
folder was easier than I thought.
I’m also working with a slightly different schema to drop the unmaintainable emoji slugs, so learning the sqlite3 syntax for selectively dumping columns was very helpful!
Still a few rough edges to knock off, mostly around making sure I can successfully fetch folks’ pages and find the webring links on them, but it’s close!
Update 2025-05-25: OwnYourGram retired in 2024 and Glitch retired project hosting in 2025. I've updated links below to point to archives where possible.
This post gives more technical detail for the recent talk that I gave at Bring-a-Hack NYC. In it, I describe a system that copies posts from Ghost Party's Instagram automatically to the Ghost Party Website at ghostparty.today.
This roughly works like so:
One missing step in that rough outline is the bit where we tell OwnYourGram *how* to post to our site via Micropub, as well as making sure that OwnYourGram can prove that we have given it permission to post there. Many Micropub clients support IndieAuth as a way to do this permission-granting step (known as authorization). With a typical personal website, I'd delegate this process to indieauth.com, which lets you offload the step of "proving" that you own the website where you're trying to post by instead letting you log into an existing social network account.
ghostparty.today is a shared website, so I thought a simpler way to approach this would be to create a custom authorization endpoint that accepts a password. I could give this password to other GHOST PARTY folks and they can post whatever they like.
Some nice folks in the IndieWeb community had already created an authorization endpoint that works this way, called selfauth. I created a pretty simplistic port of selfauth from PHP to JavaScript using NodeJS on the awesome Glitch social code hosting platform. It needs some more robust error checking, and some micropub clients seem to have trouble with it, but it works with OwnYourGram and Quill, so it's Good Enough for now!
If you're interested, you can find the archived source for this endpoint here: https://git.schmarty.net/schmarty/befitting-price
Finally, there's the Micropub endpoint itself. I based mine off of this Glitch micropub project, created by Adam Bachman at IndieWebCamp Baltimore this year. Specfically, I took the bits that verify the access token. From there, it was a matter of stumbling through various bits of handling a Micropub post, pulling in extra libraries as I needed them. I slowly added support for saving uploaded files to our Neocities site via the Neocities API, then for generating an HTML snippet for the new post, and finally for adding the post to the site's main page.
The process for adding the generated post content to the site itself is extremely retro. The Micropub endpoint actually downloads the HTML from https://ghostparty.today/index.html. Inside there is a comment that looks like:
<!-- NEW POSTS BELOW -->
So the new contents for index.html is everything before the comment, the comment, the new content, and then everything after the comment. Once that's constructed, the endpoint uploads index.html back to Neocities with the new contents.
You can find the source for the Micropub endpoint here: https://git.schmarty.net/schmarty/prism-dirt
It should be possible to fork the project on Glitch and configure it to work with your own Neocities site! You'll need to use cURL with the Neocities API to generate an authorization token to configure the app, rather than saving username and password info in the project!
The GHOST PARTY site follows an extremely old-school publishing model where each post only exists as a small section of HTML on a single long page. This complicates things a bit because interacting with posts on the IndieWeb typically requires three things:
Microformats2 parsers like X-Ray do a great job at parsing HTML page into meaningful data, but the programs that use the parsed data usually stop at the first thing they find. So, if I ask X-Ray to parse https://ghostparty.today/, this is what it sees:
{ "data":{ "type":"card", "name":"GHOST PARTY", "url":"https://ghostparty.today/", "photo":"https://ghostparty.neocities.org/ghost-party-logo.png" }, "url":"https://ghostparty.today/", "code":200 }
This is great in that it contains the "identity" information that we want the page to have. However, that page also has a feed! How can I get at that? By telling X-Ray to only look at a fragment of the page! Here's how X-Ray sees https://ghostparty.today/#feed:
{ "data":{ "type":"feed", "items":[ { "type":"entry", "published":"2018-03-07T16:44:16+00:00", "url":"https://ghostparty.today/#2018-03-07-181020", "photo":["https://ghostparty.today/uploads/2018-03-07/181020-ig0LN1JG.jpg"], "content":{"text":"snow day selfie #snowday #snowfie #ghoststories"} }, { "type":"entry", ... }, ... ], "url":"https://ghostparty.today/#feed", "code":200, "info":{"found_fragment":true} } }
Data! Nice! So if we want to follow ghostparty.today with an indie reader, we use https://ghostparty.today/#feed
as the URL.
Looking at the content of the feed, you'll notice that the individual items have a url property which also has a fragment ID in the URL. This allows IndieWeb sites to interact with a particular post, even though it's one of many on the same page. For example, I was able to create an RSVP post to the most recent Ghost Party show, and (by looking just at the content in the fragment at https://ghostparty.today/#show-2018-02-14
) my site was able to pick up the machine-readable details about the event to display them on my own site.
The microformats markup for the h-card and h-feed are built into the index.html file itself and don't change very often. However, for each new micropub post we need to convert those properties into a chunk of HTML for the index.html page that also contains the microformats h-entry markup. To do this, I made this Handlebars template:
{% raw %}<div class="h-entry" id="{{ mp-slug }}">
<a class="u-author" href="/"></a>
<a class="u-url" href="#{{ mp-slug }}">{{#if name}}<h2 class="p-name">{{ name }}</h2>{{/if}}</a>
<time class="dt-published" datetime="{{ published }}"></time>
{{#if video}}
{{#each video}}
<video controls loop class="u-video" src="{{ . }}"></video>
{{/each}}
{{ else }}
{{#each photo}}
<img class="u-photo" src="{{ . }}" />
{{/each}}
{{#each audio}}
<audio class="u-audio" src="{{ . }}"></audio>
{{/each}}
{{/if}}
<div class="e-content">{{ content }}</div>
</div>
<img src="ghosts.gif" />
{% endraw %}
Not so bad!
Here's a collection of some of the other libraries and docs I used to pull this thing together: