css-bin-bits

css-bin-bits is a CSS file that establishes an API for binary operations of
--number-vars clamped to the +/- 16bit range [-65535, 65535]

With css-bin-bits, you'll be able to:
  • read & write individual bits of a number
  • execute Modulo operations
  • bit-shift left/right
  • combine numbers using standard logic gates (AND, NAND, OR, NOR, XOR)
  • and more, all from your CSS

No JS is involved, but it requires CSS @property support from the Houdini spec.

Install:

npm install css-bin-bits

Import:

import "node_modules/css-bin-bits/bin-bits.css"OR
Use your favorite NPM CDN and include it on your page for small projects:
<link rel="stylesheet" type="text/css" href="https://unpkg.com/css-bin-bits@0.0.3/bin-bits.css">

Input → css-bin-bits

To attach the css-bin-bits API to an element, add the data attribute: data-bin-bits="a"
Now you can pass a number into one of the entry points for rounding or truncation.
The number can be an expression, calc(), or var() that results in a number.
Units (such as px, %, or deg) must not be part of the input.

Rounding:

<div data-bin-bits="a" style="--bin-a-round: -1.5;"></div>
<div data-bin-bits="a" style="--bin-a-round: 1 + 0.499;"></div>
<div data-bin-bits="a" style="--bin-a-round: (40 * 1000 + 1) * -2;"></div>

The input --bin-a-round is clamped first to +/- 16bits [-65535, 65535], then the rounding algorithm (+/- 0.5 away-from-zero) is applied.
Therefore the above numbers, respectively, produce: -2, 1, -65535

Truncation:

<div data-bin-bits="a" style="--bin-a-trunc: -1.5;"></div>
<div data-bin-bits="a" style="--bin-a-trunc: 1 + 0.499;"></div>
<div data-bin-bits="a" style="--bin-a-trunc: 67001;"></div>

The input --bin-a-trunc is clamped first to +/- 16bits [-65535, 65535], then truncated towards 0.
Therefore the above numbers, respectively, produce: -1, 1, 65535

If both *-round and *-trunc are provided, the value from *-round is used and *-trunc is ignored.

Overriding Bits:

<div data-bin-bits="a" style="--bin-a-trunc: 5; --bin-a-1: 0;"></div>
<div data-bin-bits="a" style="--bin-a-trunc: 5; --bin-a-2: 1;"></div>
<div data-bin-bits="a" style="--bin-a-trunc: 5; --bin-a-3: 0; --bin-a-4: 1;"></div>

Given the inputs above, respectively, you get:
5 (0000000000000101) with bit 1 set to 0 (0000000000000100) results in 4
5 (0000000000000101) with bit 2 set to 1 (0000000000000111) results in 7
5 (0000000000000101) with bit 3 set to 0 and bit 4 set to 1 (0000000000001001) results in 9

Each of the 16 bits from 1 to 16 can be overwritten as expected:
--bin-a-1: 0; sets the first (least significant) bit to 0
--bin-a-9: 1; sets the ninth bit to 1
--bin-a-16: 1; sets the sixteenth (most significant) bit to 1

Caution! ⚠️

If you override a bit, it must be set directly to 1 or 0, or be a calc/min/max/clamp that results in a unitless number that would round to 1 or 0.
for example:
--bin-a-1: 1.3; does not work
--bin-a-1: 1 + 0.3; does not work
--bin-a-1: calc(1.3); will work
--bin-a-1: calc(1.5); does not work
--bin-a-1: clamp(0, 1.5, 1); will work
Arbitrarily-bad-things will happen if any of them are set to anything other than 1 or 0, behavior is not defined or to be relied upon for any other values!

Multiple input sets on a single element

<div data-bin-bits="a b c"
  style="
    --bin-a-round: -1.5;
    --bin-b-trunc: 99.9;
    --bin-c-round: 0;
    --bin-c-3: 1;
  "
></div>

You can use up to 6 input sets on a single element.
The sets are named a, b, c, d, e, and f.
The css-bin-bits API becomes available for an element, for each input, by placing the set's name in the space-delimited data-bin-bits attribute value, like in the example above.
You can provide input values from your CSS files as long as the data-bin-bits attribute is set accordingly. (the input values do not need to be set from the inline style="" attribute).

css-bin-bits → Output

The inputs --bin-a-sign, --bin-a-1, --bin-a-16, --bin-f-8, etc are all outputs as well.
The sign output will contain either 1 or -1.
Each of the bits contains the expected 0 or 1: either its override, or the original expected bit from the absolute value* of the 16bit input number.
All other output from an input set is an integer result of a calculation from those bits.


* I am looking for strong opinions on this! (especially ones backed with a use case in CSS)
Cards on the table, I don't know if binary math on integers is useful in CSS because I've never considered it as a possibility when creating something.
As a result, I do no not know the use cases beyond generically helping with patterns; So negative numbers are currently handled minimally.
(In this early first release, negative numbers are treated as their absolute value, then the sign value is only considered on signed outputs.)
Should the bits from a negative number input at least be inverted? (ehh..)
Should they use two's complement? (technically, probably yes, but subjectively in this context...?)
Would either of those directions help MORE in the world of CSS art and design?
Please contact me (Jane) on Twitter with any feedback!


Direct values and their NOT values:

<div data-bin-bits="a" style="--bin-a-round: -1.5;"></div>
<div data-bin-bits="a" style="--bin-a-round: 999; --bin-a-1: 0;"></div>
<div data-bin-bits="a" style="--bin-a-round: 64537; --bin-a-3: 1;"></div>

Given the inputs above, --bin-a-VAL will contain 2, 998, and 64541 respectively.
This is the absolute value of the bits.
Given the same inputs, --bin-a-NOT will contain 65533, 64537, and 995 respectively.
This is the NOT of absolute value of the bits.

Given the inputs above, --bin-a-SIGNED will contain -2, 998, and 64541 respectively.
This is the absolute value of the bits with the original sign.
Given the same inputs, --bin-a-SIGNED-NOT will contain 65533, -64537, and -995 respectively.
This is the NOT of absolute value of the bits with the opposite of the original sign.

Helpful flag outputs:

<div data-bin-bits="a" style="--bin-a-trunc: -1.5;"></div>
<div data-bin-bits="a" style="--bin-a-trunc: 1 + 0.499;"></div>
<div data-bin-bits="a" style="--bin-a-trunc: 67001;"></div>

--bin-a-POS will contain a 1 if sign is 1, and will contain a 0 if sign is -1.
--bin-a-NEG will contain a 0 if sign is 1, and will contain a 1 if sign is -1.
--bin-a-EVEN will contain a 1 if the first bit is 0, else it will contain a 0.
--bin-a-ODD is an alias of the first bit. (Will be 1 for odd numbers, else 0.)

Modulo:

<div data-bin-bits="a" style="--bin-a-trunc: 9374; --bin-a-mod: 5;"></div>
<div data-bin-bits="a" style="--bin-a-trunc: 3; --bin-a-mod: 3;"></div>
<div data-bin-bits="a" style="--bin-a-trunc: 6; --bin-a-1: 1; --bin-a-mod: 10;"></div>

Given the inputs and --bin-a-mod value above, respectively, --bin-a-MOD will be: 4, 0, 7
The input (lowercase variable --bin-a-mod) must be a positive integer greater than 0.

Thank you, Ana!

Bit Shifting

<div data-bin-bits="a" style="--bin-a-trunc: -23; --bin-a-shift: 1;"></div>

--bin-a-shift must be an integer in range [0, 15]
Given the input and shift value above, you get 3 different outputs:
--bin-a-LEFT will be: 46 (absolute value of the input, shifted left 1)
--bin-a-RIGHT will be: 11 (absolute value of the input, shifted right 1 and truncated to int)
--bin-a-SIGNED-RIGHT will be: -32779
SIGNED-RIGHT is the absolute value of the input, shifted right 1 (and left-filled with 1s because the input is negative)
If passed into another input set:
<div data-bin-bits="a b" style="--bin-a-trunc: -23; --bin-a-shift: 1; --bin-b-trunc: var(--bin-a-SIGNED-RIGHT);"></div>
the bits from #16 to #1 will be 1000000000001011.
⚠️ Before a version 1.0.0 release, the bit form of negative numbers will likely change and the span of 0's here are likely to be 1s from a Two's Complement representation. (see note above)

Logical Operations Between Two Input Sets

The 6 input sets can be logically combined to produce their own set by adding its name to the data attribute: data-bin-bits="a b ab"
The available sets are named:
ab, ac, ad, ae, af,
bc, bd, be, bf,
cd, ce, cf,
de, df,
ef
Any or all of them can be added to a single element.
Just like the original input sets, the resulting bits can be read and/or overwritten:
--bin-ab-1 ... --bin-ab-16

Choosing the operation:

By default, the 16 bits from each of the left and right sets will be combined with XOR.
To change the opperation, assign one of the following operators:
var(--bin-NOT)
var(--bin-AND)
var(--bin-NAND)
var(--bin-OR)
var(--bin-NOR)
var(--bin-XOR)
To the set's op prop:
--bin-ab-op: var(--bin-NAND);
The ab bit set would now represent a NAND b
(note the operators are global and do not specify a set in their name, unlike all other css-bin-bits variables)

Output operations:

The output API documented previously also works on combined sets.
Therefore, --bin-ab-NOT, --bin-ab-EVEN, --bin-ab-shift & --bin-ab-RIGHT/LEFT/SIGNED-RIGHT, --bin-ab-mod & --bin-ab-MOD, etc, all behave the same way, producing an integer calculated from the bits in this set.
Just like the single input set's outputs, you can pass any into another single-letter set to perform further operations
<div data-bin-bits="a b ab c d bc cd e f ef"
  style="
    --bin-a-trunc: 5;
    --bin-b-trunc: 3;
    --bin-c-trunc: var(--bin-a-trunc);
    --bin-bc-op: var(--bin-OR);
    --bin-d-trunc: var(--bin-b-trunc);
    --bin-cd-op: var(--bin-AND);
    --bin-e-trunc: var(--bin-bc-VAL);
    --bin-f-trunc: var(--bin-cd-NOT);
    --bin-ef-op: var(--bin-AND);
  "
></div>

Given the input above, --bin-ab-VAL will be 6 from a XOR b,
and, --bin-ef-VAL will be 6 from c = a, d = b, then, effectively:
(a OR b) AND (NOT (a AND b))
Which is the same as an XOR.

Live demo: