@markmiro/hairy 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,3 +1,128 @@
1
- # Temporary Holding Version
1
+ # hairy
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Fur for your buttons. `hairy` draws an animated coat of hair behind a button's label, on the GPU with WebGL2. The fur rises on hover, poofs up on click, and bends as the pointer brushes across it.
4
+
5
+ ```sh
6
+ npm i @markmiro/hairy
7
+ ```
8
+
9
+ Or install it straight from GitHub. Each push to `main` builds the package onto the `release` branch, and each new version is tagged:
10
+
11
+ ```jsonc
12
+ {
13
+ "dependencies": {
14
+ "@markmiro/hairy": "github:markmiro/hairy#v0.1.0", // a pinned version
15
+ // "@markmiro/hairy": "github:markmiro/hairy#semver:^0.1.0", // a version range
16
+ // "@markmiro/hairy": "github:markmiro/hairy#release", // the latest build of main
17
+ }
18
+ }
19
+ ```
20
+
21
+ ## Use
22
+
23
+ ```js
24
+ import { hairy } from "@markmiro/hairy"
25
+
26
+ const fur = hairy(document.querySelector("#adopt"), {
27
+ preset: "plush",
28
+ color: "#ff4d70",
29
+ label: "auto",
30
+ })
31
+ ```
32
+
33
+ It works on a button you already have: no wrapper, and no global CSS. While the fur is mounted, `hairy` sets the element's `position` (if it's static), `isolation`, `background-color`, with `label` its `color`, and with a halo behind the label its `text-shadow`. `fur.destroy()` puts them back.
34
+
35
+ ### React
36
+
37
+ ```tsx
38
+ import { useHairy } from "@markmiro/hairy/react"
39
+
40
+ function AdoptButton() {
41
+ const ref = useHairy<HTMLButtonElement>({ preset: "plush", color: "#ff4d70", label: "auto" })
42
+ return <button ref={ref}>Adopt a cat</button>
43
+ }
44
+ ```
45
+
46
+ ## Options
47
+
48
+ | Option | |
49
+ | --- | --- |
50
+ | `preset` | `"plush"` (default), `"teddy"`, `"fluffy"`, `"fuzzy"`, `"shaggy"`, `"mane"`, and the fun ones: `"tinsel"`, `"coil"`, `"mustache"`, `"bristle"` |
51
+ | `color` | The fur's color, any CSS color. Defaults to the element's background color. |
52
+ | `label` | `"auto"` picks a readable label color by APCA contrast; a CSS color sets it; leave it out to keep the element's own. On a pattern, `"auto"` looks for a color that reads on the ground and the markings alike. |
53
+ | `scale` | Hair size. At 2 the hairs are twice as long and thick, and fewer. In em, like `"1em"`, it follows the element's font size (see below). Defaults to the `--hairy-scale` CSS variable, or 1. |
54
+ | `translate` | Moves the fur down, in CSS px; below 0 moves it up. Hair combs down, so it hangs further below the element than it reaches above it, and the label can look high: a few px up, like `-4`, centers the fur on it. Defaults to 0. |
55
+ | `wideGamut` | Draw in Display P3 on screens that support it. Defaults to `true`. |
56
+ | `pixelRatio` | Canvas pixels per CSS pixel. Defaults to the screen's, up to 2. Set it higher to bake large images. |
57
+
58
+ ### Hair that follows the font size
59
+
60
+ By default the hairs are sized in CSS px, so they stay the same size when a button shrinks. Give `scale` in em and they're sized from the element's font size instead, where 16px is `1em`:
61
+
62
+ ```js
63
+ hairy(button, { scale: "1em" }) // a 16px button gets the default hair, a 32px one hair twice the size
64
+ hairy(button, { scale: "1.5em" }) // half as big again, at any font size
65
+ ```
66
+
67
+ The number before `em` is the multiplier, so `"1em"` at a 16px font is the same as `scale: 1`. When the font size changes, say a smaller one on phones, the hairs follow, along with how far they reach (`fur.reach`) and the room the canvas leaves for them. They're measured whenever the element resizes, which a font-size change does unless the element's size is fixed.
68
+
69
+ `scale` can also come from CSS, with the `--hairy-scale` variable on the element or an ancestor. It takes the same values, a number or an em value, and `scale` overrides it:
70
+
71
+ ```css
72
+ .hero { --hairy-scale: 1.5em; }
73
+ ```
74
+
75
+ Every look and motion setting is grouped, and overrides the preset's value:
76
+
77
+ ```js
78
+ hairy(button, {
79
+ preset: "fluffy",
80
+ shade: { lightness, shadows, highlights, vividness },
81
+ shine: { amount, position, spread, edgeShadow },
82
+ hair: { density, length, thickness, taper, softness, curl, droop, lengthVariation, messiness, fuzz, distance, type, typeAmount, tightness, end },
83
+ material: { type, amount },
84
+ pattern: { type, colors, scale },
85
+ halo: { when, shade, size, strength },
86
+ glitter: { density, size, sizeVariation },
87
+ rest: { static },
88
+ hover: { static, stretch, lightness, sway, speed, damping },
89
+ leave: { speed, damping },
90
+ click: { static, stretch, lightness, riseSpeed, riseDamping, hold, fallSpeed, fallDamping },
91
+ brush: { amount, speed, damping },
92
+ })
93
+ ```
94
+
95
+ `static` is how far the hairs stand up: on their own at rest, then further on hover, then further still on a click. The playground lets you tune all of these visually and copies the code for you.
96
+
97
+ A few options pick from a list instead of taking a number:
98
+
99
+ | Option | |
100
+ | --- | --- |
101
+ | `hair.type` | `"straight"` (default), `"wavy"`, `"curly"` (coils that loop back on themselves) or `"frizzy"` (random kinks and flyaways). `typeAmount` sets how strong they are and `tightness` how many fit along each hair. |
102
+ | `hair.end` | `"round"` (default), `"sharp"` (narrows to a point) or `"square"` (cut straight across). |
103
+ | `material.type` | `"natural"` (default) or `"glitter"` (flakes that twinkle as the fur moves). `material.amount` sets how bright the flashes are, and `glitter` sets how many flakes there are, how big, and how much their sizes differ. |
104
+ | `pattern.type` | `"none"` (default), `"zebra"`, `"tiger"`, `"dalmatian"`, `"leopard"` or `"giraffe"`: a coat pattern. Each hair takes the color where it grows, so the markings have soft, hairy edges. `pattern.scale` sets how big the stripes, spots or patches are (1 the usual size). |
105
+ | `pattern.colors` | `"natural"` (default) uses the animal's own colors, whatever `color` is. `"tinted"` makes them from `color`: the markings stay dark, and a giraffe's lines light. |
106
+ | `halo.when` | A soft glow behind the label in the fur's base color, so it reads where the fur alone doesn't, as on a zebra's white and black at once. `"auto"` (default) adds it only when a pattern needs one; `"always"` or `"never"`. `halo.shade` moves its shade away from the label's, darker under a light label and lighter under a dark one (0.3 by default, in L from 0 to 1); `halo.size` spreads the glow and `halo.strength` sets how opaque it is (1 the usual for both). |
107
+
108
+ ## The fur
109
+
110
+ | | |
111
+ | --- | --- |
112
+ | `fur.update(options)` | Changes some options. Groups merge value by value. |
113
+ | `fur.set(options)` | Replaces all the options. |
114
+ | `fur.play("hover" \| "leave" \| "click")` | Plays a motion as if the pointer did it. |
115
+ | `fur.label` | `{ color, contrast, halo }`: the label color in use, its APCA Lc on the fur (over a pattern's markings too, or against the halo backing it), and whether a halo backs it, when `label` is set. |
116
+ | `fur.reach` | How far, in CSS px, the longest hairs can reach past the element's box, to keep things clear of the fur. With an em `scale`, it changes with the font size. |
117
+ | `fur.destroy()` | Removes the fur. |
118
+
119
+ `supportsFur()`, also exported, says whether this browser can draw fur.
120
+
121
+ ## Notes
122
+
123
+ - The fur is an enhancement. It needs WebGL2 on a real GPU; without it (or with only a slow software renderer), `hairy()` leaves the element exactly as it was, and `supportsFur()` returns false.
124
+ - Every fur on a page shares one WebGL2 context, so there's no limit on how many you mount. Each draws into its own canvas behind its element.
125
+ - Each fur is a cached frame until it's hovered, so only the hovered button animates.
126
+ - It honors `prefers-reduced-motion`: the fur moves straight to where it rests, with no springs or poof.
127
+ - Keyboard focus lifts the fur like hover, and Enter or Space plays the click.
128
+ - The module touches no browser APIs until `hairy()` is called, so it can be imported during server rendering.