Friday, December 31, 2021

santa baby, virtual antibody puppets in p5

 My friend Sophie likes making music videos, lately in the context of the pandemic: Everything's ClosedTwo Darn Shots, and most recently with a Christmas theme, Antibodies (to the tune of "Santa Baby"):

Early on I was helping her punch up the lyrics and the script direction was 

Chorus of ten little antibodies singing “Jingle Bell Rock” on a neutral syllable so sweetly.  With a background that might be inside a human body?

I came up with a virtual puppet prototype - use the mouse to swing 'em back and forth, and either click or press space to have them open up their mouths.


I liked the idea of a synchronized choir like that! Her collaborator/puppeteer Alex had enough greenscreen mojo to create a similar effect for the final video, but using the same puppets (which admittedly was a better style match, though Sophie would have been ok with the switch of style for the "inside view")

Anyway, it's so gratifying to be able to throw something like that together in like 30-90 minutes! 

If Sophie had gone this route I would have looked for ways to make things a little less in lockstep, like a little bit of random delay before mouths opened/shut and some tweaks to the angles, so it looked a bit less mechanical. Also I might have replaced the background I stole from some google search with something more illustrated looking, maybe moving - maybe the effect I had with blurble...


Thursday, December 23, 2021

Monday, December 20, 2021

css 2021

The State of CSS 2021 - conclusions are here, the summary from the newsletter "Frontend Focus" said:

  • When it comes to frameworks, Tailwind CSS usage is up 13% year-on-year, but Bootstrap still takes the top spot.
  • 46% of respondents detailed how they actively use Firefox for development.
  • Over 80% of respondents have now used CSS Grid.
  • A near 99%(!) of those filling out the survey have used Flexbox.
  • Curiously, nearly 50% of respondents are said to be 'happily using one or more CSS-in-JS library'.
  • Details on rise of CSS Custom Properties, and much, much more — this is all well worth digging into.
I get Frontend Focus sent to me as a newsletter, it's usually worth skimming through at least

Thursday, December 16, 2021

a deep dive into very clever and very evil hacking

A deep dive into an NSO zero-click iMessage exploit: Remote Code Execution - the attacker sends a "GIF", but it's not really a GIF, it's these ancient weird file formats that do such wacky compression that they can be broken and allowing memory to be written where it shouldn't... 

Monday, December 13, 2021

truly global radio

Want to listen to radio stations from all over the globe? With a real globe? So cool!

notes for future tiny gif animations for backpacks

 I bought a Pix backpack, it has a 16x20 grid of squares on its surface and comes with an app you can sideload looping animations from, as well as edit your own.

Recently I've had fun success using it band events; as a standalone billboard, or as part of my costume (for halloween or for Winter-y "blinky light parades". 

But I just realized: A. there's a feature to load an arbitrary GIF animation into the app (but it has to be sized to 16x20) B. this bag can display far more colors than the 16 that are baked into the app's editor. 

This is exciting! I have enough tools w/ P5 and what not to make my own animations. The Pix app also lets you just type in some text to scroll, but it's rather plain and boring. 

16x20 is going to be a serious challenge, though... like just over a quarter of the pixels I had in my 40x30 small gif cinema.

Anyway, I don't like talking about projects when still in nascent stages, but I just wanted to grab some links before closing my browser window for now - here is a tutorial for making smoothly looping animations (in Processing, I'm sure the same idea is easy to switch into P5) and more importantly here is making GIFs in P5. 

Sunday, December 12, 2021

the 3 pillars of modern CSS

 Modern CSS in a nutshell mentions flexbox, CSS Grid, and newest to me, CSS Custom Properties that seem like they can do variables ala SASS... but even better they are dynamic.

Wednesday, December 8, 2021

ctrl-s or cmd-s listeners to save in vanilla js

 I'm rewriting my blog's homebrew content management pages.

One idiosyncratic feature I like for my own use: my brain sometimes assumes "cmd-s" (ctrl-s in windows speak) should save the current entry, just like I saving a file in a local editor, the fact that I'm working in a browser be damned.

Here is the code I came up with. (I wrote similar stuff a long while back, but it was tied into JQuery, and now I'm all about the Vanilla baby!)

First, adding a listener when the page is loaded:

document.addEventListener('DOMContentLoaded', setupCmdSListeners, false);

Now on my page I have different fields that submit to different forms, so I'll just have it so the main textarea for each content type fakes a click on the submit button for that page:

function setupCmdSListeners(){
    setupCmdSListener('rawguts','submitraw');
    setupCmdSListener('quickguts','submitquick');    
}

We have to curry a function so that each listener knows which submit button is relevant

function setupCmdSListener(textareaid, submitid){
    document.getElementById(textareaid).addEventListener('keydown', 
                (event) => {cmdSListener(event,submitid) });   
}

And here's the actual listener:

function cmdSListener(event,submitid){
    if((event.ctrlKey || event.metaKey) && event.which == 83) {
        document.getElementById(submitid).click();
        event.preventDefault();
        return false;
    }
}

Pretty simple! A page could also just set up the listener for the whole page, and avoid the currying. Sometimes it's easier to write some code than train my brain and hands not to expect something like ctrl-S/cmd-S to work.



Tuesday, December 7, 2021

quick note to self: php mkdir has recursive mode

I was all set to make a half-assed (like, Unix slashes only, choking on filenames that had escaped slashes, etc) recursive directory maker (i.e. for /foo/bar/something , make foo if it doesn't exist, then foo/bar if that doesn't exist, etc) but realized it's built into PHP mkdir:

mkdir($finaldir,0755,true);

The final param is whether to do it recursively.

It's stuff like this that make me appreciate PHP a bit... like more so Perl or a lot of other languages / toolkits, thoughtful useful stuff is built in...

Saturday, December 4, 2021

quick note: download files via curl, with redirects

To download a file via curl:

 curl -L https://someurl/somefile --output somefile

The -L is useful if there are redirects. 

defending php with the importance of context

I found a pretty good article discussing Why developers hate PHP. (Spoiler: it starts seeming to support the negative vibe, but then comes around to a more balanced position.)

You can tell I'm getting defensive about PHP when I link to Taking PHP Seriously, but it has been interesting exploring why it is sometimes hated with such vehemence. (That last article, it reminds me of the old old rant Object Oriented Programming Oversold! , except OOPO is punching up and the anti-PHP stuff is generally punching down.)

(That said... some of PHP's stickiness has to do with WordPress, and... I do not enjoy configuring or making up custom widgets for WordPress...)

As I ponder on it... yeah, there's much I can't disagree with - the API is a mess, and I am completely reliant on Google to relookup every function - some of that is my age/learning style and some of that is the style of the times, but still. (On the other hand, so many of the edge-case-y "how can you live like this" parallel critique of javascript, like when equality and null/undefined and implied type conversion get weird... and this drives some people nuts but I am unconvinced it causes that many problems in real environments.)

But I still think that developers who hate it SO MUCH... well, it reminds me of my friend Jon, who recently dabbled with a toy project to learn TypeScript a bit. He didn't like TypeScript, and when I looked at his code, I saw he was trying to bring over the same OO principles from his other work, and I agreed that was a terrible fit for the language. (I didn't try to convince him that OO was indeed oversold, but as I think about it... the idea of bundling data and operations kind of goes against the grain of functional programming, and its love of immutability?) Basically, it's the wrong tool for the job, or at least for the mindset.

Anyway. My current lens for looking at the world, especially the world of tech, is "Holism vs Reductionism". Reductionism as a term has a bit of a stigma, but it's absolutely a critical half of programming- taking a complex problem and breaking it into steps is the bread and butter of programming, and functional programming and avoiding side effects - isolating - are all critical bits. 

But Holism is important too, because it is understanding how all the parts come together. ("Bugs are an emergent property" is my current thinking, so if you're not looking at things how and where they connect, you do not understand the system.) And I think a lot of the classic developer type discounts the need for the holistic and context sensitive understanding.

So, it's all about context. And I think that applies to PHP - deployment is acknowledged as its sweet spot. And I wouldn't want to do big engineering with it, but man... keeping simple things simple is so critical, that it makes up for the sometimes glaring flaws in the language. That wonky API... I mean it sometimes seems aggressively and gratuitously inconsistent, but having so many functions handy compares favorably to, say, bad old days of Perl (where everything was a install from CPAN) or even modern React (where create-react-app pulls down gigs and gigs of libraries, just in case, each having its own set of documentation if it IS the part of the kitchen sink you've learned about and want to use.)

Of course it's a mix of language and API/toolkit. I mean, it's weird that PHP is a language per se... that's where its immaturity most shines through. 

I mean, maybe if Rust or some other language was quite as easy to deploy, and had outputting DOM bits so baked into it, it would be worth checking out, but I'm not sure if such a thing exists, or would have enough of an audience to be worth learning (and figuring out how to install on my cheap rented webspace, which honestly is a big part of my loyalty to PHP...)

Heh, besides refactoring my blog's backend editor from - gak- ancient Perl CGI to PHP, the other thing that got me going here was working up a quick and easy page to display a pile of memes I had collected: kirk.is/memes/ - I feel like this one would might have been useful a full times as i read through the PHP hate pages...



Friday, December 3, 2021

edge is the new sleaze

Man, Microsoft's Edge browser has gotten really sleazy. It's one thing to know that Google, FB, et al are tracking you - depending on how uptight you are about privacy you might not care that much - but to ally with a "buy now pay later" company is downright bizarre, and then when you try to leave you get hit with slogans like 

“Microsoft Edge runs on the same technology as Chrome, with the added trust of Microsoft.”

“That browser is so 2008! Do you know what’s new? Microsoft Edge.”

“I hate saving money,” said no one ever. Microsoft Edge is the best browser for online shopping.

Yikes. 

Wednesday, December 1, 2021

storybook: consider changing your default viewport (mobile view to default)

 Just a quick note... Storybook defaults with a way of quickly switching between viewports, good for bouncing between Desktop-ish vs more mobile or tablet sizes:


The trouble is it resets when the page is reloaded, so since I'm working on a mobile-scale problem, I'd prefer to (at least temporarily) change the default.

Easy enough according to the storybook docs - I didn't really want to modify the menu item, just figure out what the defaults were named, so the only code I needed to add to our preview.tsx was

export const parameters = {
  viewport: {
    defaultViewport: 'mobile2',
  },

I suppose a team should figure out if responsive, full-width is the best starting default always, or if they want to lean into being more "mobile-first" by changing the default. It's all too easy for developers to get into a mode of "well it looks good on my large laptop in chrome, so it's probably fine." (of course narrowing the viewport is just one factor of proper mobile testing - but an important one.)

(Of course, I could have gotten similar results by using Devtools' device toolbar, but then there are other complications in terms of the rest of the Storybook trimmings around the content.)

Tuesday, November 30, 2021

javascript snippet: link-ify currently selected text in textarea

I'm rewriting my blog's backend edit features in PHP and vanilla javascript... admittedly that's a cringe-inducing mis-ambition of a target platform for some, but certainly a step up from the ancient Perl CGI and JQuery I was using before.

Features of my editing including managing image uploads w/ resizing and gallery-making, a tag system, special "blockquote" content, twitter quotes, etc. 

A while back I talked about linky, my preferred way of shortcutting having to type <a href=""></a> all the time - basically for the current selection only, if it starts with "http", assume that is a URL we are linking to, otherwise assume the selection is the clickable text.

Here is vanilla JS to do that (vs my earlier jQuery textrange() stuff)

function linky(textarea){
    const len = textarea.value.length;
    const start = textarea.selectionStart;
    const end = textarea.selectionEnd;
    const sel = textarea.value.substring(start, end);    
    const replace = sel.indexOf('http') === 0 ? 
        `<a href="${sel}"></a>` 
        : `<a href="">${sel}</a>`;
    textarea.value =  textarea.value.substring(0,start) 
                + replace 
                + textarea.value.substring(end,len);
}

(vanilla text replace cribbed from here... don't know or care that much if this busts on IE)

"textarea" is the DOM element... I was thinking about how to know which textarea last had the focus, so I can apply the same function to multiple textareas without having to give each textarea its own launching link. I think I will add an onfocus to each textarea that is eligible for linky-fication to register itself globally, and the last such textarea to have done so will be the one used in the function. Crude but good enough.

Stepping back up to the UX of it... the fidliness/ugliness of <a href=""></a> was the kind of thing that led Aaron Swartz and John Gruber to invent Markdown - as Swartz put it "Why should I have to cover everything in annoying pointy brackets just so it knows what I mean?" 

I don't mind pointy brackets that much - I have a strong preference for working on the level of the target  platform (i.e. DOM markup and javascript that are consumed natively by the browser without a pre-processing step) so long as that level is still reasonably expressive and not too onerous, so I wouldn't consider a separate markup language. (The markdown-ish thing thing I do do, though, is replace \n with <br>...)

The other UX solution for composing links I often see is a popup dialog that has "URL" in one field and clickable text in another. I always get confused about which is which, so I find my "put in href initial boilerplate" method more well-suited to my thinking.


Friday, November 26, 2021

those little lego screens



 

sticky feet! well, a footer

 Interesting, according to this page,

html, body { height: 100%;}

body > footer {
  position: sticky;
  top: 100vh;
}

is a nice sticky footer, even though it looks like it should be underneath the page.

some possibly false memories about the history of making false memories

The other day I sang the praises of "Pixelmator Pro" for Mac, which let me clean up this photo of a neighbor's pup (name of Star)


It was very easy to do with Pixelmator Pro's "repair" tool. Some Android phones having a built-in "Magic Eraser" is a significant (but I suspect temporary?) advantage over Apple iPhones, but I was able to make the same effect happen on the iOS version of Pixelmator, so the difference is just the advantage of having something built-in. (And the power of brand name/labeling)

I'm having a bit of possible Mandela Effect though, about similar retouching in history... in the 1984 Olympics, runners Mary Decker and Zola Budd bumped and head a tripping incident, and the aftermath was captured in some iconic photos by David Burnett, most notably this one:

I have a strong memory about this photo (even though I would have been in 4th grade at the time) Specifically: a version was published that had (digitally?) removed the walkie-talkie antenna that seems to be jutting from Decker's jaw, and that was considered controversial; but Google as I might, I can only find the un-retouched version, and no mention of such a controversy, so now I'm second guessing myself! Was it a big issue? Did I even see an article or watch a segment on it?

Of course, there are long-standing tropes about how not photo tells the complete truth. You never know what's outside the boundary of the photo; either physically out of frame or in the moments before and after. And with long-standing photo manipulation - culminating in deepfake videos - seeing will no longer be believing. (It almost makes Soviet Era Photo Retouching seem quaint.) 

With the rise of deepfakes... I don't know. Reputation (of the individuals in question and of news sources) is going to be increasingly important - like more and more will depend on how much you trust the chain of people saying they say what they said, or didn't say. (And in a nation with a long standing tradition of left-leaning but basically factually reliable news against more explicitly partisan and ideological "truthiness" sources on the right, this is a bad development.) But any newspaper or magazine has always been able to fabricate quotes, so having to trust your source is nothing new, but now even eyewitness video cannot be considered canonical evidence unless confirmed by the people in it.


Wednesday, November 24, 2021

debugging a mysterious webpack slowness

 I have always relied on the kindness of coworkers for managing build tools, so this case study in debugging a mysteriously slow webpack was eye-opening.

pixelmator pro is a great choice for mac

I've always been just a dabbler with Paintshop-style programs, but Pixelmator Pro is a pretty great, friendly tool, and for $40 is a worthy upgrade over the free version.

I especially dig "magic erase" and smart select tools. Here's me recreating an effect I first saw at the Second Line Brass Band website:


The new Select Subject tool got confused by the horn, understandably enough, but with the smart select it was easy to duplicate the main layer, use smart select to paint over the background and then delete, and then turn the background layer into B+W.

Maybe I should hunt down some tutorials, there's a lot this software can do. I've never gotten my head around tools for making vector graphics, it might be a good segway into that. 

Tuesday, November 23, 2021

thoughts on the oculus 2

Spending Thanksgiving week with my Mom and Aunt and we decided to get the house a Oculus 2 (my Aunt was really blown away by the VR they had at the Van Gogh exhibit.)

I already had the PS4 VR setup, but the Oculus 2 is so much better and has made me more sanguine about VR in general...  having the visor free and untethered is an absolute game changer. (Also the PS4 setup was an enormous pain in the butt to set up, all these connecting wires going every which way)

The Oculus controllers are a good mix of classic controllers (like with thumbsticks) and things that work as hand surrogates, with a convention of "mitten grip" button with the middle finger to grab items and the trigger finger to shoot.

The primary menu interface paradigm has use use either controller like a laser pointer pistol, with a thin beam always showing where you're pointing. (The Vader Immortal game I tried had an amusing UI with clunky virtual pushbuttons I liked as well.) 

Another frequent trope I've noticed in VR is often they have you move by pointing to where you want to stand next and then you "teleport", rather than more smoothly moving there like in a game. I think that might tie into preventing motion sickness? 

The visor uses a camera on the thing to aid with head tracking (it complained about the room being too dark when I turned off lights because of some leakage at the bottom of the visor.)  And there's a super cool mode it slips into where it shows you what the camera is seeing, albeit in grainy black and white.  So it's like you're in your space, but not really in your space.

Combining that "AR" showing of the room with the laser pointer guns - they cleverly have you use specify the boundaries it's safe to walk around in in your physical area, using the laser pointer gun almost like it was a big welding torch on the floor. Later, if you're in an immersive game and go too far, edges popup, kind of like the boundaries of the holodeck peeking through.  

I've heard rumors that the Apple Glasses will be AR, virtual objects projected on the background of your actual space, and the b+w room mode reinforces my idea that that might be the future - it was an interesting rebuttal temper one of the problems of VR, how isolated and vulnerable you are

Penny Arcade Sanctum 

Also, just having stereo speakers near your ears vs PS4 VR's ear buds felt much more real - albeit at the cost of annoying anyone in the vicinity.  Which is similar to one of the few drawbacks of the Oculus relative to the PS4 VR: by default it's not mirroring anything to an external screen, so any potential social-in-person aspect is greatly diminished.

Still looking for great games in it. I guess that's the other drawback relative to game consoles: there's not much in terms of first party development, like big names you know you can trust. 

Also it's a little light on some of the non-game "experiences", though maybe my aunt and I haven't looked hard enough.

Though typing this after about an hour or so of play, I realize I'm a little headachey. 

So yeah, pretty cool product overall, and while I'm not sure if VR will just become the next Kinect or 3D TV (just another passing fad that might stick around but barely) or if it's the future-future, this is a nice mid-price way to see some cool stuff. 

Thursday, November 18, 2021

the Right Thing

Great Quote:
 These arguments were the lifeblood of the hacker community. Sometimes people would literally scream at each other, insisting on a certain kind of coding scheme for an assembler, or a specific type of interface, or a particular feature in a computer language. These differences would have hackers banging on the blackboard or throwing chalk across the room. It wasn't so much a battle of egos as it was an attempt to figure out what "The Right Thing" was. The term had special meaning to the hackers. The Right Thing implied that to any problem, whether a programming dilemma, a hardware interface mismatch, or a question of software architecture, a solution existed that was just . . . it. The perfect algorithm. You'd have hacked right into the sweet spot, and anyone with half a brain would see that the straight line between two points had been drawn, and there was no sense trying to top it. "The Right Thing," Gosper would later explain, "very specifically meant the unique, correct, elegant solution . . . the thing that satisfied all the constraints at the same time, which everyone seemed to believe existed for most problems."  
--Steven Levy in "Hackers: Heroes of the Computer Revolution" 
I stole a paperback of this from my Uncle's library back in the day. It covers the MIT / Tech Model Railroad Club of the 60s, the Apple II etc homebrew hackers of the 70s, and then the early home computer programmers of the 80s.

On a less dev-y note: The book probably was pretty inspirational for me, and I see how well this "Right Thing" attitude parallels how I see the world, the central relic of the religiosity of my youth... I have this sense that there is an ultimate objective Truth- the "view from God's Throne" of the world for matters of What We Should Do (whether or not there is a divine butt in that chair!) and any view holdable by humans is only an approximation of that divine perspective - and any earthly authority is only valid to the extent to the extent that it is an accurate reflection of that view. (This is in contrast to an everything is subjective view... it leans into uncertainty in a way that both hard core fundamentalism and post-modern-ish existenitalism lack.)

Wednesday, November 17, 2021

fun with filters and locale selectors

For work I'm refactoring our locale selector. We have content that varies by country, and then is offered in different languages within each country. 

Our old way of providing a language picker tended to provide the country and language in the locale of the current page... but that's a huge matrix! You have list each country/language IN each of the separate languages. It's error prone: here's a page where our Belgium-in-French page lists the name of the Spanish language in English:

Also, the file to maintain that was over 7000 lines of JSON. (Yes I'm sure it compresses but still.)

Plus I don't believe it's best practice, as FlagsAreNotLanguages puts it:

If you’re linking to pages in German and Chinese, label them as ‘Deutsch’ and ‘中文’ — not ‘German’ and ‘Chinese’. 

It's more complicated when you have different content in different counties - but we are using Flags correctly, for the country and not the language :-) 

I made the pitch to our PO, with screenshots from Disney+ and Salesforce.com. (I had to cherry pick my examples, sites can be all over the place with this stuff.)

I found it a rather pleasant exercise to refactor the redundant JSON from 7300 lines to around 200. 

The other pleasant part was using JS/ECMAscript's new(ish) array operators - ... notation, filter, map, reduce, etc... they are such a good match with JSON overall, both are very expressive without being too arcane. I mulled over a few styles of keeping the lists sorted (wanting this to be roughly alphabetical, but having the current locale first, and then followed by other languages in the same country, then everything else.) I tried some weird ideas with sorting, but then realized I could just preserve the original locale order as listed in the JSON file, but filtered as:

    // finally put the current locale first, 
    // followed by other languages in same country, 
    // then everything else
    return [
      ...items.filter(exactLocaleMatch),
      ...items.filter(justCountryMatch),
      ...items.filter(noCountryMatch),
    ];

with little helper functions above that

    const exactLocaleMatch = (item: { id: string }) => 
        item.id === currentLocale;
  const justCountryMatch = (item: { id: string }) =>
    !exactLocaleMatch(item) && item.id.substring(3) 
        === currentLocale.substring(3);
  const noCountryMatch = (item: { id: string }) =>
    item.id.substring(3) !== currentLocale.substring(3);

Very satisfying work.

Friday, November 12, 2021

excellent quote

"Programming is very Kafkaesque. You're dealing with this arbitrary system that no one will explain to you because no one actually understands all of it. There are bugs."

--official-kircheis 

Thursday, November 11, 2021

escher-sketcher

I made an isometric doodling program escher-sketcher based on an old Antic magazine type-in program. You can see the first and second rough drafts that tried to recreate the old way of doing the isometric artwork... but really P5's WebGL 3D mode and "normalMaterial" coloring was kind of made for it. 

Arrow keys plus 'a' and 'z' draw (space bar toggles between drawing and moving) ...you can hit 'r' to have it generate random trails... 

Mouse changes the viewing angle, 't' toggles a classic "orthogonal" view vs regular perspective.. 'c' clears, space toggles drawing vs just moving, 's' for screenshots.

Tuesday, November 9, 2021

drawing and recording shapes, then scaling fun for a birthday card

 I like making little interactive virtual birthday and/or holiday cards for friends, usually in P5.

Since my friend Jon helped me with the 1D-fisheye zoom effect I put the same effect on x and y and came up with this fun zoomy effect card:

To record the freehand shapes (which, you know, add a personal touch relative to just having formatted text display) I wrote little programs for recording shapes using screen coordinates and recording normalized (0<n<1) values. You freehand draw shapes, and then press any key and it spits out the drawing commands. (I guess I could have recorded as JSON, but whatever). 

On a related note (the joy of making little art programs) this medium article Clive Thompson describing his doodling technique and a little browser app he made for it. Charming and pleasant!

Monday, November 1, 2021

lightweight frontend build tools

Three years ago I bemoaned the need for build systems for frontend work - or maybe more specifically, the assumption many libraries now make that you won't be solely relying on <script src="" />.

Asking around, besides parcel that I wrote about above, there's Snowpack and Vite. Here's an article comparing them.

I do miss the wild west days. I'm slow to really drink the assumption that "if something is worth making it's worth putting in git with a build process to deploy".

Sunday, October 31, 2021

some css boilerplate

Here's some boilerplate for text-heavy CSS, via 100 Bytes of CSS to look great everywhere:

html {

  max-width: 70ch;

  padding: 3em 1em;

  margin: auto;

  line-height: 1.75;

  font-size: 1.25em;


Saturday, October 30, 2021

zoom / fisheye effect in 1D

I've been messing around with my timelines displays, the somewhat self-absorbed problem of trying to cram an entire life (thus far) into a screenish-sized-space. After messing around with arch displays I'm back to linear... except I want to make it interactive, so like the time line expands where the cursor or finger is pointing, but then when you move out, it returns and you still get the "full life" effect. I think this kind of interactivity might let you get a better sense of time passing relative to a scrolling screen, and more engaging that just cramming everything to fit.

But I wasn't sure what a good "zooming" algorithm would be! I defined the problem pretty well: I can normalize every date value on a number line to a value between 0 and 1, and do similar for where the pointer is relative to the full width of the time line. Then the new Value function takes the normalized date value and the normalized pointer value and returns the new (normalized) position for the date.

But, I'm not too bright with this kind of stuff! My first thought was values near the pointer are greatly shoved, so I should multiply the difference from the date value and the pointer value by how close they are, so values further away barely moved from their original position. But that led to something very jumpy, since values right AT the pointer hop from one side to another at an almost infinite degree, or whatever the max value allowed is. 

To get to a better algorithm, I made a nice interactive display toy for it. The new algorithm says, take a weighted average of the current value and one of the endpoints (1 or 0, whichever is on the opposite side of the pointer) - if the pointer is far away from the value, the weighting is biased toward the original value, but as the pointer is closer to the value the average is weighted more toward that far away endpoint. I tweaked the display so each dot showed a line pointing to where it started from, and the height corresponding to the strength of the push:

So, that was better, but not great. Luckily, my friend Jon (the same guy who helped me with working with a hex grid) is much better at this kind of geometry and math stuff than I am. (He does graphic tool work for a very very major tech company and also, like, attends origami conventions.) He was able to use my display tool and realize what I was looking for was a sigmoid function, and he graced me with the following:

// A simple sigmoid function
const f = (x, m, k) => .5 * (x - m) / (k + Math.abs(x - m)) + .5;
// The normalized date
const x = val;
// The normalized date to zoom in around
const m = zoompoint;
// How much to distort, lower = more distorted
const k = .5;
// Normalize the output of the sigmoid function to [0, 1]
return (f(x, m, k) - f(0, m, k)) / (f(1, m, k) - f(0, m, k));

You can see the result here - I tweaked the k constant to .2, and added back the visualization that shows how strong the push is. 


Monday, October 25, 2021

helmet - use arrays not fragments for repetitive blocks (ala hreflang)

Getting exposed to a few new things at work... I just mentioned the hreflang tag, but also on the React side we use Helmet, which seems a clever way of avoiding dueling, incompatible meta tags even if you have a weird bunch of components trying to put stuff up there.

I was trying put all the hreflang tags I had to make as a React Fragment (<></>) but they were getting eaten up - looking back I'm pretty sure that's because Helmet assumes all the stuff it needs to worry about is a 'top level' component. So instead of a Fragement, I had a function return an array of individual tags, and that seemed to work fine.

designer wisdom

Last February at my job, the designer Sous bestowed this wisdom:

When in doubt, 16px


hreflang

 Hadn't head too much about "hreflang", the meta tag to tell search engines etc "the equivalent of this page exists in locale X at URL Y" but this seems like a pretty thorough introduction.

when heroes look like they're breaking down

 I'm always kind of intrigued how every generation of Apple hardware gets a new set of background images, shots used on the glamour/hero product shots. But lately it feels like they may be running out of ideas? Like both the Macbook Pro and iPhone Pro resemble various ways I've seen screens fail because of physical impact....I've definitely seen screens like this:


And while usually the light-saber looking beam of a pure color extends the whole screen, seeing these beams zoom in as I scroll through the promo page took me aback:

Would love to see a gallery of these images over the years, and fund out about the process of selecting them... is it like an honor for one person, or by committee, or what.

Saturday, October 23, 2021

hothouse gullibility flowers

It's probably not great to attack a book when I've barely started it, but early in Chapter 1 of Steven Pinker's "Rationality", he mentions an old chestnut of a riddle
On a field there is a patch of weeds. Every day the patch doubles in size. It takes 30 days for the patch to cover the whole field. How long did it take for the patch to cover half the field?
and Pinker bemoans that most people won't get the correct answer (29 days, i.e. since it doubles daily, the day before the final day it was half the final size.)

He explains
Human intuition doesn't grasp exponential (geometric) growth, namely something that rises at a rising rate, proportional to how large it already is, such as compound interest, economic growth, and the spread of a contagious disease. People mistake it for steady creep or slight acceleration, and their imaginations don't keep up with the relentless doubling.
But what a fantastical setup that riddle is! Like any physical model would show us that no patch of weeds on earth could have that kind of behavior "steadily" over 30 days. To show that to myself, I hacked my version of Conway's Game of Life to be even simpler : every alive cell lives on, and every dead cell with at least one alive neighbor is born. The result is visually boring - a square that grows from the middle of the screen. And checking the population numbers, they are far from doubling. The rate that the square can grow is clearly bounded by its boundary, the 2D "surface area" where it has new fertile territory to move into, and so there's no way its actual area could keep doubling. And similarly, I can't think of a mechanism and environment that would support much of anything from having consistent doubling behavior for 30 days!

I find these thought experiments infuriating when they are used as examples of people's "irrationality". It's akin to economists thinking people are irrational for preferring receiving ten dollars now vs thirty dollars a year from now. In an uncertain world, any real world test subject is absolutely correct to be suspicious of a test program reliably running over the course of a year (especially when its business model seems to have big deal of just giving away money!)

I used to think of these as "casino-ish" problems- like, they are customized to prey on human's response at this attractive edge of artifice. But I guess I'd say they're "hothouse gullibility" thought experiments - they take for granted that OF COURSE the research is trustworthy, or that a patch of weeds that doubles every day for 30 days is a meaningful prototype to ponder. They are merely interrogating how well subjects can navigate a completely artificial environment of simplifying assumptions.

UPDATE:
Update... later in explaining why people make this kind of error he does say
we might point to the ephemerality of exponential processes in natural environments (prior to historical innovations like economic growth and compound interest). Things that can't go on forever don't, and organisms can multiply only to the point where they deplete, foul, or saturate their environments, bending the exponential curve into an S. This includes pandemics, which peter out once enough susceptible hosts in the herd are killed or develop immunity.
So I think it still forces the question: how meaningful are these contrived examples in generating useful knowledge about the world?

Wednesday, October 20, 2021

Saturday, October 16, 2021

10 Years of Devblogging!

Wow - I've been personal blogging daily since 2000 but in some ways I'm more surprised that this devblog is 10 today! Here's my first post.

So, no real intent of stopping, and I'll leave you today with this thought (aka the Robustness Principle) which I encountered on Usenet back in the 90s:

Be conservative in what you send and liberal in what you accept.
-- RFC 791, paraphrased 

My belief in the second part ("liberal in what you accept") is part of why I am less into TypeScript as some folks :-D Maybe the result of two and a half decades of Perl and Javascript.

Wednesday, October 13, 2021

creating and positioning circle image clipping masks in p5.js

 As I previously blogged, image clipping masks in p5 are possible, but not easy to work with.  Stackoverflow explained how to use offscreen graphics to allow "drawn" masks (vs using another image as the alpha channel) but that graphic is stretched to cover the whole image - and then when you position the now masked image, you have to deal with the width and height of the rectangle of the original image, even though you've clipped to some arbitrary part within that rectangle.

I thought I would simplify things by going for circles (vs my original idea of arches, which would be very trick to calculate bounding boxes for and reposition). Even then it wouldn't be trivial; for aesthetic reasons I might prefer a mask where the circle was on one side or the other of the original rectangle, and of course some images are "landscape-mode" and others are "portrait-mode":

It took me longer to wrestle with this problem than I want to admit (I mean not THAT long, but still) - a few false starts where I thought maybe I had to treat portrait vs landscape separately. But the "aha!" moments (more like, "oh, right...") came from noticing that A. for simplicity we are assuming that at least one of the sides of the original image is the size of circle B. we can figure out the "ratio" of how much larger the original image was than the circle for each side C. those ratios tell us how much we have to shift the positioning of the whole image in order to get the circle back to where we want it. 


(I actually added two masks for each image, one a half-filter for the whole image rectangle so we can see what the actual corners we have to work with are, and then the circle which is the end goal)

The heart of it is these two functions. The first one (I take care to preload() the images so we can get the reliably get the width and height of the raw images) makes the mask:

function makeMaskedImage(img, dir){
    const circleMask = createGraphics(img.width,img.height);
  // fill a soft mask so we see the whole frame,
  // for educational purposes
    circleMask.fill('rgba(0, 0, 0, .5)');
    circleMask.rect(0,0,img.width,img.height);
  
    const sz = min(img.width,img.height);
    const wRatio = img.width/sz;
    const hRatio = img.height/sz;
    
  //hard circle mask
    circleMask.fill('rgba(0, 0, 0, 1)');
    circleMask.circle(
        (sz * wRatio) / 2 + (dir * ((sz  * wRatio - sz)/2)),
        (sz * hRatio) / 2 + (dir * ((sz  * hRatio - sz)/2)),
    sz);
    img.mask(circleMask);
  return {img, wRatio, hRatio, dir};
}
"dir" -  a value between -1 and 1 representing the offset of the circle from the center to the edge for the longer side - is kind of an aesthetic choice for each image, so it's a parameter.  The calculated wRatio and hRatio are how long that side is relative to the size of the circle - so if the image was square to begin with, these would both be "1", meaning each side is as big as circle.

The math is fiddly, but basically says "put the circle in the center, then move it "dir" times one-half how much bigger that side is than the base circle.  We then return information we will need to properly display it: the image itself (now with the masked affixed to it), the two ratios, and the "dir" indication which way (and technically how much, tho mostly -1, 0, or 1) the circle is offset from center along the longest side.

The drawing code then is
function drawMaskedImage(maskedImg, cx, cy, sz, ){
    const {img, wRatio, hRatio, dir} = maskedImg;
    const trueWidth = sz * wRatio;
    const trueHeight =  sz * hRatio;
    const xCenter = cx - (trueWidth/2);
    const yCenter = cy - (trueHeight/2)
    
    const xWiggle = ((trueWidth - sz) / 2) * dir;
    const yWiggle = ((trueHeight - sz) / 2) * dir;   

    image(img,xCenter - xWiggle,yCenter - yWiggle,trueWidth,trueHeight);   
}
Elsewhere in the code we make sure we're in rectMode(CENTER); a bit easier to deal with the middle of the image than work from the corners. We formulate the whole rectangular images dimensions at this arbitrary size sz, figure out the x and y center as if the circle was in the middle of that rectangle, then xWiggle and yWiggle figure out how to counterbalance the positioning of the rectangle in terms of the circle relative to it.

Phew! I don't know if there's a more elegant way but this seems pretty good to me.

Random Tangent:
I'm currently trying to figure out what I can learn from people who LOVE strongly typed languages, much more than I do. I mean, I wouldn't mind using typescript to describe what keys should be in {img, wRatio, hRatio, dir}, but here's the thing: as I evolved this code, at first I thought "dir" should be a fixed value, like "LEFT" or "TOP" or "CENTER", but then realizing that could also be 1,0,-1 - and in those cases do double duty as a math multiplier. (In fact, my new system would let me do other offsets, in case you didn't want the spotlight quite bumping up against one edge or the other.) Like I suspect someone who wasn't comfortable with loosely typed languages (as I am after decades of Perl and then Javascript) would have noticed that flexibility and possible repurposing so easily.


Tuesday, October 12, 2021

canistilluse.com

 https://www.canistilluse.com/ - kind of a joke site, about the kerfuffle on deprecating certain hoary old things in browsers, like "alert()" boxes.

I hate that trend towards removing innocuous things in the name of progress. I'm coming to terms with how URLs aren't forever, nor websites, but going out of your way to break other people's stuff seems rude.

Google's Dark Mode

 At work our designer Dean mentioned:

Dunno if anyone took notice but Google Search recently released a Dark Mode (If that’s your jam, it is mine!)  which from a design POV was well done IMHO, good contrast but nicely nuanced meaning it’s not harsh but refreshing on the eyes because they didn’t go full black (an easy mistake to make) and chose softer colors  (also an in-thing to not go full bright color) to match 

I mentioned I personally am not a fan of dark modes (especially if they're used as a default) even though I know they are popular- but for people with Astigmatism - a big part of the population, they are problematic because the eye has to open more and that leads to blurriness for them Dean wrote back with

Ya agree, not for everyone. Just geeking and appreciating a good implementation of it as generally I think most folks think it’s a flipping of a switch but there’s actually more than less nuance in dark modes to deal with the accessibility issues, keep within the brand, consider dimensionality, statefulness, and elevations etc.

But they are popular enough that I have to assume it's not just "looks cool' and some people do find it more relaxing on the eyes - wonder if it's the sample people who prefer e-ink to reading on tablets with more visual "pop"


Understanding Figma Hierarchy

 Figma has a hierarchy, but it’s not always clearly represented in its web UI.

In particular, if you are looking at a specific “page” in Figma (for example, maybe a designer is updating a component, and you are a developer ) it might not be clear how to get to that page’s File’s Project ID. (For my team, Project IDs are very important for the tool we built to pull Figma data from its API.)

Empirically, Figma’s hierarchy seems to be:

Organization > Team > Project > File > Page

So for a Monster Global team member checking out some new changes to Buttons, that might be 

Monster (org) > Core (team) > Pre-Production (project) > Core [Jira Ticket Num] (file) > Button (page)

This hierarchy is not well represented by the Figma sidebar. For instance, logging in and heading back to figma.com/ I see this:


But often our Designers hand around links straight to Pages, and the Project ID isn't in the URL... in that case the stylized "F" has a menu with a "Back to Files" option that (if permissions are right and the creek don't rise) takes you to the page for the whole Project, which has the Project ID embedded in its URL.

Tuesday, October 5, 2021

form submit POST/GET echo

Awhile back I wrote https://stuff.alienbill.com/formecho/ as a simple "echo whatever POST or GET info was passed in" that came in useful at work today.

Thursday, September 30, 2021

css accent-color

CSS for accent-color looks pretty dope! I think the lack of reliable CSS styling on native form elements- an that goes WAY back to Web 1.0 days - has created so much bogus work making fakey components. 

jira: origins

Finding by our scrummaster Eric Stolls about Jira, a tool we rely on heavily:

 I stumbled across this today.   Fun Fact about the origin of the name Jira - The product name is a truncation of Gojira, the Japanese word for Godzilla. The name originated from a nickname Atlassian developers used to refer to Bugzilla, which was previously used internally for bug-tracking.

Heh, Bugzilla was probably a play off of the old "Mozilla". My  personal homepage is still called "Kirkzilla" as a play on that, back when it was just the html view of my Netscape bookmarks.

Friday, September 24, 2021

low-key but excellent improvement in iOS 15: calendar time editing

 Many people praise the return of the "maginify loupe" in iOS 15, letting you more easily see what you're highlighting just a bit a part from your big ol', but I just stumbled on an improvement I'm even more grateful for: iOS 14's calendar entry edit field was a mess. Here it is on iOS14 (on my sweetie Melissa's phone):


This is what you see in edit mode and go to edit the start time - a text-ish input for the time appears underneath what you clicked to edit it. I always found that unintuitive, to have 2 fields meaning the same thing, one editable - especially since it was easy to conceptually mix up with the other field beneath it, the one for End time.

But earlier I had been denoted that on my iOS 15 phone, the old "tumblers" are back!



I find those tumblers a small delight to use, just a nice little bit of kinetic action - and as you roll it around to find the hour, you get a sense of where you are are in a 12 hour period that you lack tapping in digits. Maybe more importantly, the value display is much more distinct from the value edit field. Win-win!


command line and text "fun"

In the so-old-school it’s new department, Command Line Interfaces are back - if they ever went away in the first place. 

Firebear Studio has a list of 75 Best Node.js Command Line Apps & Utilities. You can build some awesome into your installer, like parallel dynamic spinner/progress bars, giant ASCII font banners, and slick pick list and options sets.

Asciiflow is another text-based cool tool… it’s a simple diagram editor, but it outputs ASCII (both extended and old-school)…


From


to

+----------------+

|                |

|                |

|  Are we having |

|                +-----------------+

|     fun yet?   |                 |

|                |                 |

|                |                 |

+----------------+                 |

                       +-----------v-------+

                       |                   |

                       |                   |

                       |    Well Why Not?  |

                       |                   |

                       |                   |

                       |                   |

                       +-------------------+

Just make sure you are using a fixed-width font!


Tuesday, September 21, 2021

the size of boxes

Box-sizing is a minor annoyance for the modern web developer. In short, the question is “when setting the size of a DOM object, should you include the border and padding (“border-box” for the “box-sizing” property) or just the content “guts”? (“content-box”)

Historically, “content-box” was, and remains, the default, even though the consensus is “border-box” is easier to work with. So many sites will put something like

* {
  box-sizing: border-box;
}

So your design system needs to account for that - testing in an environment where that has been set. (Empirically it seems Figma uses the older content-box model.) 

  1. Right Click on an page and hit “Inspect”
  2. Under the Styles tab, hit the “+” button for “New Style Rule”
  3. Replace any class or element name with “*”
  4. Click to enter a new CSS property and enter `box-sizing: border-box;` resulting in something like 

The MDN box-sizing page goes into more detail on the options you have for it.

Monday, September 20, 2021

css resize property

You know, CSS resize property is pretty cool, for oldtimers like me it's one of those things that would have been a HUGE pain to try and implement in the browser, but now we get it for free:

resize: both;
overflow: auto;

I guess that's in the same family as setting the contenteditable property on a div:

This text can be edited by the user.!!

Obviously the DOM is so much more dynamic than it was back in the day, so it's great they added some hooks into that. (Editing the content of the div, basically making everything into a textarea... sort of weird but I guess not that much different than using developer/inspector tools

Thursday, September 16, 2021

phone goofiness and p5 instead of pixel editors

After getting a nice yellow silcone magsafe case for my iPhone, I decided to lean into the "duck" theme I stumbled on a few weeks ago:



The pixel duck was "inspired" by a google search for "pixel ducks", I then used browser-based tool piskel to roughly copy it (but using fewer colors.)

In making the actual background image, I was fumbling both with Acorn and Pixelmator... in both cases I ran into problems - either resizing without resampling, or just other basic functions. So I switched to a p5.js program. The "scale" trick to flip the image was a little wonky, and it took some finesse, but in general I like the control by doing design in code.

(Before this the theme was bumblebees, with an intermediate diver/blooper theme after I drowned buzzbuzz and had to get a new iPhone.)

Wednesday, September 15, 2021

WIP: permutation engine

A while back I showed a storybook component showing a 2D grid of combinations for props for a component library. V2 looked a little less cool but did a much better job of coming up with ALL permutations, not just the 1+1 combos you can show on a grid...



Here is the code:

import React from 'react';
import PropTestingProps from './permutation-testing.props';
import { PermutationTestingStyle } from './permutation-testing.style';
import { Button } from '../Button/button.component';

import { Checkbox } from '../Checkbox/checkbox.component';
import { Radio } from '../Radio/radio.component';
import styled from 'styled-components';
import { IconButton } from '../IconButton/icon-button.component';
import { Slider } from '../Slider/slider.component';
import { Tab } from '../Tab/tab.component';
import { TextArea } from '../TextArea/text-area.component';
import { TextField } from '../TextField/text-field.component';
import { Dropdown } from '../Dropdown/dropdown.component';
import { TypeAhead } from '../TypeAhead/type-ahead.component';

const Table = styled.table`
  th {
    font-weight: bold;
  }
  th,
  td {
    padding: 8px;
    font-family: sans-serif;
    vertical-align: middle;
    text-align: left;
  }
  margin-bottom: 16px;
`;

let count = 0;
interface ComponentPropMatrixProps {
  label: string;
  permutations: string[];
  render: React.FC;
}

const getAllSubsets = (theArray: string[]) =>
  theArray.reduce(
    (subsets, value) => subsets.concat(subsets.map((set) => [value, ...set])),
    [[] as string[]],
  );

const ComponentPropSubsetsList = (props: ComponentPropMatrixProps) => {
  const { permutations, render, label } = props;

  // get all subsets, sort on length of subset, then keep original order of elemts
  const subsets = getAllSubsets(permutations)
    .sort((a, b) => a.length - b.length)
    .map((arr) =>
      arr.sort((a, b) => permutations.indexOf(a) - permutations.indexOf(b)),
    );

  return (
    <>
      <h3>{label}</h3>
      <Table>
        <tbody>
          {subsets.map((subset) => {
            const subsetAsProps = {};
            subset.forEach((x) => {
              subsetAsProps[x] = true;
            });
            count++;
            console.log(count);
            return (
              <tr key={JSON.stringify(subset)}>
                <td>{render({ componentProps: subsetAsProps, label })}</td>
                <th>{subset.join(' ')}</th>
              </tr>
            );
          })}
        </tbody>
      </Table>
    </>
  );
};
// idle, checked, error, disabled

const buttonVariations: React.ReactChild[] = [];
for (const shape of ['rectangle', 'oval']) {
  for (const size of ['sm', 'lg']) {
    for (const ord of ['primary', 'secondary', 'tertiary']) {
      console.log(`${size} ${shape} ${ord}`);
      buttonVariations.push(
        <ComponentPropSubsetsList
          label={`Button ${size} ${shape} ${ord}`}
          permutations={['disabled']}
          render={({ componentProps, label }) => (
            <Button
              size={size}
              shape={shape}
              buttonType={ord}
              {...componentProps}
            >
              Button
            </Button>
          )}
        />,
      );
    }
  }
}

const checkboxVariations: React.ReactChild[] = [];
for (const size of ['sm', 'md']) {
  checkboxVariations.push(
    <ComponentPropSubsetsList
      label={`Checkbox ${size}`}
      permutations={['checked', 'error', 'disabled']}
      render={({ componentProps, label }) => (
        <Checkbox size={size} {...componentProps} label='Checkbox' />
      )}
    />,
  );
}

const iconButtonVariations: React.ReactChild[] = [];
for (const shape of ['square', 'circle']) {
  for (const size of ['sm', 'md', 'lg']) {
    for (const ord of ['primary', 'secondary', 'tertiary']) {
      iconButtonVariations.push(
        <ComponentPropSubsetsList
          label={`Icon Button ${size} ${shape} ${ord}`}
          permutations={['disabled']}
          render={({ componentProps, label }) => (
            <IconButton
              size={size}
              shape={shape}
              buttonType={ord}
              {...componentProps}
            >
              Icon Button
            </IconButton>
          )}
        />,
      );
    }
  }
}

const radioVariations: React.ReactChild[] = [];
for (const size of ['sm', 'md']) {
  radioVariations.push(
    <ComponentPropSubsetsList
      label={`Radio ${size}`}
      permutations={['checked', 'error', 'disabled']}
      render={({ componentProps, label }) => (
        <Radio
          size={size}
          {...componentProps}
          id={`${JSON.stringify(componentProps)}_${size}`}
          label='Checkbox'
        />
      )}
    />,
  );
}

const sliderVariations: React.ReactChild[] = [];

sliderVariations.push(
  <ComponentPropSubsetsList
    label={`Slider`}
    permutations={[]}
    render={({ componentProps, label }) => (
      <Slider {...componentProps} label='Slider' />
    )}
  />,
);

const tabVariations: React.ReactChild[] = [];

tabVariations.push(
  <ComponentPropSubsetsList
    label={`Tab`}
    permutations={[]}
    render={({ componentProps, label }) => <Tab {...componentProps}>Tab</Tab>}
  />,
);

const textAreaVariations: React.ReactChild[] = [];

textAreaVariations.push(
  <ComponentPropSubsetsList
    label={`Text Area`}
    permutations={['disabled', 'error']}
    render={({ componentProps, label }) => (
      <TextArea {...componentProps}>Some Text</TextArea>
    )}
  />,
);

const textFieldVariations: React.ReactChild[] = [];

textFieldVariations.push(
  <ComponentPropSubsetsList
    label={`Text Field`}
    permutations={['disabled', 'error']}
    render={({ componentProps, label }) => (
      <TextField value='Some Text' {...componentProps}></TextField>
    )}
  />,
);

const dropDownVariations: React.ReactChild[] = [];

dropDownVariations.push(
  <ComponentPropSubsetsList
    label={`Dropdown`}
    permutations={['disabled', 'error']}
    render={({ componentProps, label }) => (
      <Dropdown value='Some Text' {...componentProps}></Dropdown>
    )}
  />,
);

const typeAheadVariations: React.ReactChild[] = [];

typeAheadVariations.push(
  <ComponentPropSubsetsList
    label={`Type Ahead`}
    permutations={['disabled', 'error']}
    render={({ componentProps, label }) => (
      <TypeAhead value='Some Text' {...componentProps}></TypeAhead>
    )}
  />,
);

export const PermutationTesting = (props: PropTestingProps) => {
  return (
    <PermutationTestingStyle>
      {buttonVariations}
      {checkboxVariations}
      {iconButtonVariations}
      {radioVariations}
      {sliderVariations}
      {tabVariations}
      {textAreaVariations}
      {textFieldVariations}
      {dropDownVariations}
      {typeAheadVariations}
    </PermutationTestingStyle>
  );
};

As maybe you can see it's pretty easy to spin up new variants. "getAllSubsets()" is some very clever code I found online and honestly don't quite understand that makes all the permutations possible.

Monday, September 13, 2021

stuntcoding and the power of sin (and cos)

 A while back I made this (somewhat pretentious) business card:

It has a small Processing program on the back - in 2014 I impressed Ben Fry, co-founder of Processing, by having it there.

You can see the program in action at kirk.is/card/. The code is 
//spiro.pde - see processing.org
float a,b;
void draw(){
  noStroke(); fill(0,10);
  rect(0,0,100,100); fill(255);
  ellipse(50+(20*cos(a)+10*cos(b)),
          50+(20*sin(a)+10*sin(b)),
          3,3);
  a += map(mouseX,0,100,-.5,.5);
  b += map(mouseY,0,100,-.5,.5);
} //see in action: http://kirk.is/card/
It's nothing too fancy... just using the mouseX and mouseY as speed controls for a pair of nested circles - very much like a spirograph. ("draw()" is the p5 function called every tick of the clock)

One clever trick is rather than erasing the window each click, it puts a rectangle over of black at 10% transparency, leading to a nice little fade out effect and letting the user see the pattern as it is made.

But then the other day I ran across this beauty at fyprocessing on tumblr: (written in p5, processing's Javascript descendent)

which is made from this snippet, that fits in 1 280-char tweet (barely!)
t=0
draw=_=>{createCanvas(w=500,w)
n=sin
t+=.03
for(c=0;c<w;c+=31)for(r=0;r<w;r+=31)if((r+c)%2<1){beginShape(fill(0))
for(i=0;i<4;i++)for(b=i*PI/2+max(min(n(t+dist(w,w,c,r))*1.5+n(t/2)/2,1),
-1)*PI,a=b+3.9;a>b+2.3;a-=.1)vertex(c+cos(b)*32+cos(a)*18,r+n(b)*32+n(a)*18)
endShape()}}
Here's the tweet that it came from.  You can see it in an editor here.

That is amazing and beautiful, and I didn't understand it all. First I need to start to unobfuscate it: (to be fair it wasn't obfuscated to make it confusing, just concise.)

My first step was to just reformat it and change just a few of the syntatic shortcuts:
let t=0;
function draw(){
  createCanvas(w=500,w);
  let n=sin;
  t+=.03;
  for(c=0;c<w;c+=31){
    for(r=0;r<w;r+=31){
      if((r+c)%2<1){
        beginShape();
        fill(0);
        for(i=0;i<4;i++) {
          for(b=i*PI/2+max(min(n(t+dist(w,w,c,r))*1.5+n(t/2)/2,1),-1)*PI,
          a=b+3.9; a>b+2.3; a-=.1) {
            vertex(c+cos(b)*32+cos(a)*18,r+n(b)*32+n(a)*18);
          }
        }
        endShape()
      }
    }
  }
}
I swapped in `function draw()` for `draw=_=>` - I was looking up what "_" meant before I realized it was just a throw away variable for the fat arrow notation. 

He also uses "n" as a shortcut for "sin". I'm going to undo that, and then change the complicated for() loop into its "while" equivalent.
let t=0;
const SZ = 500;
function draw(){
  createCanvas(SZ,SZ);
  t+=.03;
  for(c=0;c<SZ;c+=31){
    for(r=0;r<SZ;r+=31){
      if((r+c)%2<1) {
        beginShape();
        fill(0);
        for(i=0;i<4;i++){
            b=i*PI/2+max(min(sin(t+dist(SZ,SZ,c,r))*1.5+sin(t/2)/2,1),-1)*PI;
            a=b+3.9; 
            while(a>b+2.3) {
              vertex(c+cos(b)*32+cos(a)*18,r+sin(b)*32+sin(a)*18);
              a-=.1;
            }
          }
        endShape()
      }
    }
  }
}
Also I replaced the cleverly initialized "w" variable with a big old const SZ, my favorite shortcut for the size of a thing. 

So now I'm at a place to start poking at variables. "t" seems to be the basic timer; by incrementing by more per click I can speed it up, for instance. Or make it stay at 0.0 -- things are frozen (but when you freeze time like that, many of the spinners are at odd angles... interesting ) And it's pretty clear "r" and "c" are for rows and columns - incrementing more than that "31" spaces them out.  

So we're making a bunch of shapes. 
      if((r+c)%2<1) {
is making it like a checkerboard pattern... I added
fill(200,0,0); circle(c,r,10);
after the EndShape and then I can see a the center of each formation.

So now, looking at the code... the loop with i is being done 4 times.

To try and make things more clear, I replace the vertex() in the shape
    vertex(c+cos(b)*32+cos(a)*18,r+sin(b)*32+sin(a)*18);
 with a tiny box, so I can see whats being drawn:
    rect(c+cos(b)*32+cos(a)*18,r+sin(b)*32+sin(a)*18,1,1);
 but it doesn't help me see much, I guess things are tightly packed within the shape. I see a is being adjusted by .1 each iteration... bumping that up to .3 and the way things are drawn is more clear, especially with my dots placed at the center of each thing.
So a is iterating to draw one of the 4 sides. It starts at whatever b is plus 3.9, and then goes down to b plus 2.3... in other words, ranges PI * ( 5 / 4) down to PI * (3 / 4)

So I put the vertex() line back, because now I roughly get it what's going on with the draw shape stuff. There's still some "spirograph" cleverness with a big circle plus a small circle (and at related angles) but still.

If I throw away b's fancy computed value and replace it with  b = i * PI/2; I see everything locked down- and I can add a constant to that see all the widgets move at the same angle, and 
b = mouseX / 10 +  i * PI/2;
is a very nice effect I can control. (remember, i is just 0, 1, 2 and 3, so we're just doing 4 sides of the circle, so to speak.)

So that just leaves us with this monster that effectively sets b for each spinner to a constant (outside of the i * PI /2 boost):
max(min(sin(t+dist(SZ/2,SZ/2,c,r))*1.5+sin(t/2)/2,1),-1)*PI

yikes!

One thing I notice is that dist() call - this is what sets each spinner at a different place in the math, based on its position on screen. That's pretty clever! 

Ok, so to get a grip on this, I simplify the r + c counters so I just see a single spinner:
  for(c=100;c<101;c+=31){
    for(r=100;r<101;r+=31){
this will make it easier to see the patterns in side the massive b equation.  Even just watching one alone, it has a kind of hypnotic chaos to it. 

So every draw() it is createCavas()ing, I move that to a separate setup() so it just happens once, then I actually plot *one* of the values of B for my single spinner, sort of like a cardiogram:
if(i == 0) {
  stroke(128); rect(t*6,SZ/2 + b*10,2,2); 
  stroke(200); rect(t*6,SZ/2 ,2,2); 
}
that looks pretty cool actually:

So that's informative! We see how B gets clamped at 1 and -1 - but sometimes that's at the top of the arch, and sometimes it's at the bottom

I see that one part of the equation is really the heart, everything else is min/max. So I isolate it and call it minguts:
const minguts = sin(t+dist(SZ/2,SZ/2,c,r))*1.5 + sin(t/2)/2;  
then the equation for b is much simpler:
b=i*PI/2+max(min(minguts,1),-1)*PI;
Using a variation of the "cariogram" above, I plot out minguts in red, then min(minguts,1) in green then max(min(minguts,1),-1) in blue:
So the blue line is basically what we get for b, it's the end result. And red, the minguts... looking at its euation we see it's just adding two cycles of sin waves - the larger one influenced by that dist() stuff, the smaller one changing more slowly (t/2) and having less of an effect (the /2). Then the min and the max is just putting a ceiling and a floor to it- when it's at 1 or -1, I'm betting the spinner is straight one way or the other, and all the other values represent some level of spin.

If you want to see my "testbed" I hacked apart- kind of like dissecting a frog - it's here

So wow. I am in awe of this! It uses so much spatial smarts to make something really visually appealing. (I've seen that "spinners move to form negative space circles with their neighbors around) 

You can see more of Andy Makes tweet length code examples or more of his art and games on his website.