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.

what color shall we paint the bike shed

 Today at work I had reason to mention bikeshedding, where too much time and attention is paid to not-significant-enough details. Here's another example of it used in context.

Saturday, September 4, 2021

links in the traditional blue

 Why is the sky are links on the web traditionally blue? A really fun look at very early browsers.

Fun ("fun") fact - I've had a personal start page ("kirkzilla", a play on mozilla) on line for, like, decades. At first it used a trick in early Netscape of displaying your saved bookmarks as a page, but later I made it its own thing. But I kept the colors the classic Mozilla even when I had to use CSS to do so...