craft-library 0.1.1 → 0.1.3
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 +2 -2
- package/dist/craft.js +72 -3
- package/dist/magnet.js +52 -7
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -44,7 +44,7 @@ import { LikeButton } from 'craft-library';
|
|
|
44
44
|
|
|
45
45
|
## Magnetic icon
|
|
46
46
|
|
|
47
|
-
A round search button with a brushed-metal rim, for dark backgrounds. Near the cursor the rim acts like metal near a strong magnet: the disc is drawn toward the pointer, the rim creases and trembles, its tip cracks into shards and
|
|
47
|
+
A round search button with a brushed-metal rim, for dark backgrounds. Near the cursor the rim acts like metal near a strong magnet: the disc is drawn toward the pointer, the rim creases and trembles and, where it is pulled, comes apart in small voxels of metal; its tip cracks into shards and the voxels fly off to the pointer.
|
|
48
48
|
|
|
49
49
|
```jsx
|
|
50
50
|
import { MagneticIcon } from 'craft-library';
|
|
@@ -63,7 +63,7 @@ import { MagneticIcon } from 'craft-library';
|
|
|
63
63
|
One script registers three tags. Same pieces, same props as attributes.
|
|
64
64
|
|
|
65
65
|
```html
|
|
66
|
-
<script src="https://cdn.jsdelivr.net/npm/craft-library@0
|
|
66
|
+
<script src="https://cdn.jsdelivr.net/npm/craft-library@0/dist/craft.js"></script>
|
|
67
67
|
|
|
68
68
|
<glass-nav active="0"></glass-nav> <!-- "select" event: e.detail.index -->
|
|
69
69
|
<like-button></like-button> <!-- "change" event: e.detail.liked -->
|
package/dist/craft.js
CHANGED
|
@@ -436,6 +436,7 @@
|
|
|
436
436
|
var PULL = 15, FALL = 1.1;
|
|
437
437
|
var BODY = 0.3, SHIFT = 0.14;
|
|
438
438
|
var JAG = 0.18;
|
|
439
|
+
var FRAY = 1.6, CHIPS = 90;
|
|
439
440
|
var DENT = 3.5;
|
|
440
441
|
var RATE = 90, MAXDUST = 90;
|
|
441
442
|
var ROOM = 46;
|
|
@@ -476,16 +477,24 @@
|
|
|
476
477
|
var dt = wrap(t - m.a), d = m.d;
|
|
477
478
|
return R + (d > 0 ? d * (spike(dt, width(m)) + BODY * bell(dt, 1)) : d * bell(dt, 0.7)) - 0.12 * d * bell(wrap(dt - Math.PI), 1.3);
|
|
478
479
|
}
|
|
480
|
+
function strainOf(m) {
|
|
481
|
+
return clamp((Math.abs(m.d) + 3 * m.heat) / PULL, 0, 1);
|
|
482
|
+
}
|
|
483
|
+
function damage(m, t) {
|
|
484
|
+
return clamp((Math.abs(m.d) + 3 * m.heat) / 9, 0, 1) * bell(wrap(t - m.a), 0.7);
|
|
485
|
+
}
|
|
479
486
|
function rimPath(m, now) {
|
|
480
|
-
var pts = [], strain =
|
|
487
|
+
var pts = [], strain = strainOf(m), i, t, r, env, sharp, fray, n, p, a, b, c, e;
|
|
481
488
|
for (i = 0; i < N; i++) {
|
|
482
489
|
t = i / N * 2 * Math.PI;
|
|
483
490
|
r = radius(m, t);
|
|
484
491
|
env = bell(wrap(t - m.a), 0.6) * strain;
|
|
485
492
|
sharp = env > 0.12;
|
|
486
|
-
|
|
493
|
+
fray = sharp ? 0 : clamp((damage(m, t) - 0.15) / 0.85, 0, 1);
|
|
494
|
+
if (sharp && i % 3 || fray > 0 && i % 2) continue;
|
|
487
495
|
if (sharp) r += 2 * JAG * PULL * env * tremble(i, now);
|
|
488
|
-
|
|
496
|
+
else if (fray > 0) r += FRAY * fray * (hash(i, 21) - 0.62 + 0.3 * tremble(i + 500, now));
|
|
497
|
+
pts.push([m.cx + r * Math.cos(t), m.cy + r * Math.sin(t), sharp || fray > 0]);
|
|
489
498
|
}
|
|
490
499
|
n = pts.length;
|
|
491
500
|
p = "M" + n2(pts[0][0]) + " " + n2(pts[0][1]);
|
|
@@ -672,9 +681,50 @@
|
|
|
672
681
|
}
|
|
673
682
|
g.restore();
|
|
674
683
|
}
|
|
684
|
+
function chips(m, now) {
|
|
685
|
+
var t = m.t, f = crack(m)[0];
|
|
686
|
+
m.chips.forEach(function(c, k) {
|
|
687
|
+
var o = clamp((damage(m, c.a) + 0.08 * tremble(300 + k, now) - c.at) / 0.12, 0, 1), z, r, x, y, dx, dy, l;
|
|
688
|
+
if (o < 0.02) o = 0;
|
|
689
|
+
if (!o && !c.open) return;
|
|
690
|
+
c.open = o;
|
|
691
|
+
z = c.s * o;
|
|
692
|
+
r = Math.min(radius(m, c.a), f) + c.o;
|
|
693
|
+
x = m.cx + r * Math.cos(c.a);
|
|
694
|
+
y = m.cy + r * Math.sin(c.a);
|
|
695
|
+
c.el.setAttribute("x", n2(x - z / 2));
|
|
696
|
+
c.el.setAttribute("y", n2(y - z / 2));
|
|
697
|
+
c.el.setAttribute("width", n2(z));
|
|
698
|
+
c.el.setAttribute("height", n2(z));
|
|
699
|
+
if (o < 0.2) c.gone = false;
|
|
700
|
+
else if (o > 0.6 && !c.gone && t && now - c.at2 > 500 && m.dust.length < MAXDUST) {
|
|
701
|
+
c.gone = true;
|
|
702
|
+
c.at2 = now;
|
|
703
|
+
dx = C + t.x - x;
|
|
704
|
+
dy = C + t.y - y;
|
|
705
|
+
l = Math.hypot(dx, dy) || 1;
|
|
706
|
+
m.dust.push({
|
|
707
|
+
x,
|
|
708
|
+
y,
|
|
709
|
+
vx: dx / l * 10,
|
|
710
|
+
vy: dy / l * 10,
|
|
711
|
+
hx: x,
|
|
712
|
+
hy: y,
|
|
713
|
+
s: c.s,
|
|
714
|
+
k: 1,
|
|
715
|
+
age: 0,
|
|
716
|
+
life: 0.7 + Math.random() * 0.6,
|
|
717
|
+
back: false,
|
|
718
|
+
spin: Math.random() < 0.5 ? -1 : 1,
|
|
719
|
+
c: Math.random() < 0.35 ? 190 : 255
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
});
|
|
723
|
+
}
|
|
675
724
|
function draw(m, now) {
|
|
676
725
|
var s = m.box.width / 40, dpr = Math.min(2, devicePixelRatio || 1), size = Math.round((40 + 2 * ROOM) * s * dpr), f = crack(m), g = m.g;
|
|
677
726
|
m.rim.setAttribute("d", rimPath(m, now));
|
|
727
|
+
chips(m, now);
|
|
678
728
|
m.grad.setAttribute("cx", n2(m.cx));
|
|
679
729
|
m.grad.setAttribute("cy", n2(m.cy));
|
|
680
730
|
m.stops[0].setAttribute("offset", (f[0] / 40).toFixed(4));
|
|
@@ -741,6 +791,24 @@
|
|
|
741
791
|
cv.className = "mag-dust";
|
|
742
792
|
cv.setAttribute("aria-hidden", "true");
|
|
743
793
|
el.insertBefore(cv, icon);
|
|
794
|
+
var mask = svg.querySelector("mask"), holes = [], k, q;
|
|
795
|
+
for (k = 0; mask && k < CHIPS; k++) {
|
|
796
|
+
q = document.createElementNS("http://www.w3.org/2000/svg", "rect");
|
|
797
|
+
q.setAttribute("fill", "#000");
|
|
798
|
+
q.setAttribute("width", 0);
|
|
799
|
+
q.setAttribute("height", 0);
|
|
800
|
+
mask.appendChild(q);
|
|
801
|
+
holes.push({
|
|
802
|
+
el: q,
|
|
803
|
+
a: (k + 0.8 * hash(k, 11)) / CHIPS * 2 * Math.PI,
|
|
804
|
+
s: 0.9 + 1.5 * hash(k, 12),
|
|
805
|
+
o: 0.2 + 1.1 * (hash(k, 13) - 0.5),
|
|
806
|
+
at: 0.08 + 0.72 * hash(k, 14),
|
|
807
|
+
open: 0,
|
|
808
|
+
gone: false,
|
|
809
|
+
at2: -1e9
|
|
810
|
+
});
|
|
811
|
+
}
|
|
744
812
|
el.__mag = {
|
|
745
813
|
el,
|
|
746
814
|
rim: svg.querySelector(".mag-rim"),
|
|
@@ -750,6 +818,7 @@
|
|
|
750
818
|
cv,
|
|
751
819
|
g: cv.getContext("2d"),
|
|
752
820
|
dust: [],
|
|
821
|
+
chips: holes,
|
|
753
822
|
acc: 0,
|
|
754
823
|
pace: 0,
|
|
755
824
|
lp: null,
|
package/dist/magnet.js
CHANGED
|
@@ -8,8 +8,9 @@ function engine() {
|
|
|
8
8
|
const window = {};
|
|
9
9
|
// Magnetic icon · Craft Library, by Sara Lobo
|
|
10
10
|
// Every .mag on the page reads the pointer and its rim follows it while it is near: the disc is drawn toward it, the
|
|
11
|
-
// strained rim creases and
|
|
12
|
-
// and the rim springs back with a wobble
|
|
11
|
+
// strained rim creases, trembles and comes apart where it is pulled, the tip cracks into shards, and voxels fly to
|
|
12
|
+
// the pointer. Let go, and the rim springs back with a wobble, closing up as the loose voxels return to it. Pairs with
|
|
13
|
+
// magnet.css.
|
|
13
14
|
// Buttons added later: magnetize(root). To steer one without a mouse (a demo, a tour): magnetPoint(button, x, y) with
|
|
14
15
|
// a point on screen, and magnetPoint(button, null) to hand it back to the pointer.
|
|
15
16
|
(function () {
|
|
@@ -18,6 +19,7 @@ function engine() {
|
|
|
18
19
|
var PULL = 15, FALL = 1.1; // the furthest the rim reaches out, and how fast the pull drops with distance
|
|
19
20
|
var BODY = 0.3, SHIFT = 0.14; // how much of the disc goes with the tip, and how far the disc itself is drawn
|
|
20
21
|
var JAG = 0.18; // how deep the strained rim creases
|
|
22
|
+
var FRAY = 1.6, CHIPS = 90; // how far the pulled stretch of the rim frays, and how many voxels can fall out of the ring
|
|
21
23
|
var DENT = 3.5; // the furthest the rim is drawn in while the pointer is over the button
|
|
22
24
|
var RATE = 90, MAXDUST = 90; // voxels shed per second at full pull, and at most at once
|
|
23
25
|
var ROOM = 46; // room for the shards and voxels on every side of the button (matches .mag-dust)
|
|
@@ -45,18 +47,26 @@ function engine() {
|
|
|
45
47
|
return R + (d > 0 ? d * (spike(dt, width(m)) + BODY * bell(dt, 1)) : d * bell(dt, 0.7)) - 0.12 * d * bell(wrap(dt - Math.PI), 1.3);
|
|
46
48
|
}
|
|
47
49
|
|
|
50
|
+
// how hard the field strains the metal (0 at rest, 1 at a full pull), and how worn the rim is at angle t: only where
|
|
51
|
+
// the magnet pulls, beside the shards, fading out within about a quarter turn either side; the rest of the ring
|
|
52
|
+
// stays whole. Wear builds faster than the strain, so a middling pull already marks it
|
|
53
|
+
function strainOf(m) { return clamp((Math.abs(m.d) + 3 * m.heat) / PULL, 0, 1); }
|
|
54
|
+
function damage(m, t) { return clamp((Math.abs(m.d) + 3 * m.heat) / 9, 0, 1) * bell(wrap(t - m.a), 0.7); }
|
|
55
|
+
|
|
48
56
|
// the rim as one closed curve: smooth where it is calm (Catmull-Rom turned into cubic Béziers), creased into straight,
|
|
49
|
-
// trembling facets where the magnet strains it
|
|
57
|
+
// trembling facets where the magnet strains it, and frayed into short crooked facets on either side of them
|
|
50
58
|
function rimPath(m, now) {
|
|
51
|
-
var pts = [], strain =
|
|
59
|
+
var pts = [], strain = strainOf(m), i, t, r, env, sharp, fray, n, p, a, b, c, e;
|
|
52
60
|
for (i = 0; i < N; i++) {
|
|
53
61
|
t = i / N * 2 * Math.PI;
|
|
54
62
|
r = radius(m, t);
|
|
55
63
|
env = bell(wrap(t - m.a), 0.6) * strain;
|
|
56
64
|
sharp = env > 0.12;
|
|
57
|
-
|
|
65
|
+
fray = sharp ? 0 : clamp((damage(m, t) - 0.15) / 0.85, 0, 1);
|
|
66
|
+
if ((sharp && i % 3) || (fray > 0 && i % 2)) continue;
|
|
58
67
|
if (sharp) r += 2 * JAG * PULL * env * tremble(i, now);
|
|
59
|
-
|
|
68
|
+
else if (fray > 0) r += FRAY * fray * (hash(i, 21) - 0.62 + 0.3 * tremble(i + 500, now)); // each facet keeps its own dent (mostly worn in), and shivers a little
|
|
69
|
+
pts.push([m.cx + r * Math.cos(t), m.cy + r * Math.sin(t), sharp || fray > 0]);
|
|
60
70
|
}
|
|
61
71
|
n = pts.length;
|
|
62
72
|
p = 'M' + n2(pts[0][0]) + ' ' + n2(pts[0][1]);
|
|
@@ -201,9 +211,34 @@ function engine() {
|
|
|
201
211
|
g.restore();
|
|
202
212
|
}
|
|
203
213
|
|
|
214
|
+
// the voxels falling out of the rim where it's pulled: holes in the rim's mask, each opening once the metal is worn
|
|
215
|
+
// enough where it sits (and flickering at the edge of breaking), always on the rim you can see: past the crack line,
|
|
216
|
+
// where the shards take over, that's the crack line itself. The piece that falls out flies to the magnet; as the
|
|
217
|
+
// rim heals, the holes close again
|
|
218
|
+
function chips(m, now) {
|
|
219
|
+
var t = m.t, f = crack(m)[0];
|
|
220
|
+
m.chips.forEach(function (c, k) {
|
|
221
|
+
var o = clamp((damage(m, c.a) + 0.08 * tremble(300 + k, now) - c.at) / 0.12, 0, 1), z, r, x, y, dx, dy, l;
|
|
222
|
+
if (o < 0.02) o = 0; // closed means fully closed: no sliver left in the mask
|
|
223
|
+
if (!o && !c.open) return;
|
|
224
|
+
c.open = o;
|
|
225
|
+
z = c.s * o; r = Math.min(radius(m, c.a), f) + c.o; x = m.cx + r * Math.cos(c.a); y = m.cy + r * Math.sin(c.a);
|
|
226
|
+
c.el.setAttribute('x', n2(x - z / 2)); c.el.setAttribute('y', n2(y - z / 2));
|
|
227
|
+
c.el.setAttribute('width', n2(z)); c.el.setAttribute('height', n2(z));
|
|
228
|
+
if (o < 0.2) c.gone = false;
|
|
229
|
+
else if (o > 0.6 && !c.gone && t && now - c.at2 > 500 && m.dust.length < MAXDUST) {
|
|
230
|
+
c.gone = true; c.at2 = now;
|
|
231
|
+
dx = C + t.x - x; dy = C + t.y - y; l = Math.hypot(dx, dy) || 1;
|
|
232
|
+
m.dust.push({ x: x, y: y, vx: dx / l * 10, vy: dy / l * 10, hx: x, hy: y, s: c.s, k: 1, age: 0, life: 0.7 + Math.random() * 0.6,
|
|
233
|
+
back: false, spin: Math.random() < 0.5 ? -1 : 1, c: Math.random() < 0.35 ? 190 : 255 });
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
204
238
|
function draw(m, now) {
|
|
205
239
|
var s = m.box.width / 40, dpr = Math.min(2, devicePixelRatio || 1), size = Math.round((40 + 2 * ROOM) * s * dpr), f = crack(m), g = m.g;
|
|
206
240
|
m.rim.setAttribute('d', rimPath(m, now));
|
|
241
|
+
chips(m, now);
|
|
207
242
|
// the drawn rim gives way at the crack line, where the shards take over
|
|
208
243
|
m.grad.setAttribute('cx', n2(m.cx)); m.grad.setAttribute('cy', n2(m.cy));
|
|
209
244
|
m.stops[0].setAttribute('offset', (f[0] / 40).toFixed(4));
|
|
@@ -261,8 +296,18 @@ function engine() {
|
|
|
261
296
|
cv.className = 'mag-dust';
|
|
262
297
|
cv.setAttribute('aria-hidden', 'true');
|
|
263
298
|
el.insertBefore(cv, icon);
|
|
299
|
+
// the voxels that can fall out of the rim: squares in its mask, closed until the metal wears (see chips()).
|
|
300
|
+
// Each sits at its own spot round the ring, mostly biting the rim from outside, and gives way at its own strain
|
|
301
|
+
var mask = svg.querySelector('mask'), holes = [], k, q;
|
|
302
|
+
for (k = 0; mask && k < CHIPS; k++) {
|
|
303
|
+
q = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
|
|
304
|
+
q.setAttribute('fill', '#000'); q.setAttribute('width', 0); q.setAttribute('height', 0);
|
|
305
|
+
mask.appendChild(q);
|
|
306
|
+
holes.push({ el: q, a: (k + 0.8 * hash(k, 11)) / CHIPS * 2 * Math.PI, s: 0.9 + 1.5 * hash(k, 12), o: 0.2 + 1.1 * (hash(k, 13) - 0.5),
|
|
307
|
+
at: 0.08 + 0.72 * hash(k, 14), open: 0, gone: false, at2: -1e9 });
|
|
308
|
+
}
|
|
264
309
|
el.__mag = { el: el, rim: svg.querySelector('.mag-rim'), grad: svg.querySelector('radialGradient'), stops: svg.querySelectorAll('radialGradient stop'),
|
|
265
|
-
icon: icon, cv: cv, g: cv.getContext('2d'), dust: [], acc: 0, pace: 0, lp: null, ghost: null,
|
|
310
|
+
icon: icon, cv: cv, g: cv.getContext('2d'), dust: [], chips: holes, acc: 0, pace: 0, lp: null, ghost: null,
|
|
266
311
|
d: 0, v: 0, a: -Math.PI / 2, heat: 0, cx: C, cy: C, ix: 0, iy: 0, t: null, still: true };
|
|
267
312
|
mags.push(el.__mag);
|
|
268
313
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "craft-library",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Craft Library by Sara Lobo: a liquid glass nav bar, a bursting heart and a magnetic icon, for React or plain HTML",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
}
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
|
-
"esbuild": "0.24.0"
|
|
30
|
+
"esbuild": "0.24.0",
|
|
31
|
+
"playwright-core": "^1.61.1"
|
|
31
32
|
},
|
|
32
33
|
"scripts": {
|
|
33
34
|
"build": "node build.mjs",
|