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


 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... 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)');
    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)');
        (sz * wRatio) / 2 + (dir * ((sz  * wRatio - sz)/2)),
        (sz * hRatio) / 2 + (dir * ((sz  * hRatio - sz)/2)),
  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 - 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 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 as a simple "echo whatever POST or GET info was passed in" that came in useful at work today.