craft-library 0.1.2 → 0.2.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 +27 -4
- package/dist/card-load.js +2 -0
- package/dist/card-mount.js +32 -0
- package/dist/card-visa.png +0 -0
- package/dist/card.cdn.js +593 -0
- package/dist/card.js +596 -0
- package/dist/cdn.js +4 -3
- package/dist/craft.js +65 -6
- package/dist/elements.js +20 -2
- package/dist/index.js +15 -1
- package/dist/magnet.js +16 -15
- package/package.json +10 -4
package/dist/cdn.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
// the one-file build for a <script> tag: registers the
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
// the one-file build for a <script> tag: registers the four tags and exposes the mount functions (the card's 3D scene
|
|
2
|
+
// is fetched from next to this file the first time a card is mounted)
|
|
3
|
+
import { mountLike, mountMagnet, mountGlassNav, mountIridescentCard } from './elements.js';
|
|
4
|
+
window.CraftLibrary = { mountLike, mountMagnet, mountGlassNav, mountIridescentCard };
|
package/dist/craft.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* Craft Library, by Sara Lobo · liquid glass nav bar, bursting heart, magnetic icon · <glass-nav>, <like-button>, <magnetic-icon> */
|
|
1
|
+
/* Craft Library, by Sara Lobo · liquid glass nav bar, bursting heart, magnetic icon, iridescent card · <glass-nav>, <like-button>, <magnetic-icon>, <iridescent-card> */
|
|
2
2
|
(() => {
|
|
3
3
|
// dist/style.js
|
|
4
4
|
var done = /* @__PURE__ */ new Set();
|
|
@@ -436,7 +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 =
|
|
439
|
+
var FRAY = 1.6, CHIPS = 90;
|
|
440
440
|
var DENT = 3.5;
|
|
441
441
|
var RATE = 90, MAXDUST = 90;
|
|
442
442
|
var ROOM = 46;
|
|
@@ -481,7 +481,7 @@
|
|
|
481
481
|
return clamp((Math.abs(m.d) + 3 * m.heat) / PULL, 0, 1);
|
|
482
482
|
}
|
|
483
483
|
function damage(m, t) {
|
|
484
|
-
return clamp((Math.abs(m.d) + 3 * m.heat) / 9, 0, 1) *
|
|
484
|
+
return clamp((Math.abs(m.d) + 3 * m.heat) / 9, 0, 1) * bell(wrap(t - m.a), 0.7);
|
|
485
485
|
}
|
|
486
486
|
function rimPath(m, now) {
|
|
487
487
|
var pts = [], strain = strainOf(m), i, t, r, env, sharp, fray, n, p, a, b, c, e;
|
|
@@ -682,14 +682,14 @@
|
|
|
682
682
|
g.restore();
|
|
683
683
|
}
|
|
684
684
|
function chips(m, now) {
|
|
685
|
-
var t = m.t;
|
|
685
|
+
var t = m.t, f = crack(m)[0];
|
|
686
686
|
m.chips.forEach(function(c, k) {
|
|
687
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
688
|
if (o < 0.02) o = 0;
|
|
689
689
|
if (!o && !c.open) return;
|
|
690
690
|
c.open = o;
|
|
691
691
|
z = c.s * o;
|
|
692
|
-
r = radius(m, c.a) + c.o;
|
|
692
|
+
r = Math.min(radius(m, c.a), f) + c.o;
|
|
693
693
|
x = m.cx + r * Math.cos(c.a);
|
|
694
694
|
y = m.cy + r * Math.sin(c.a);
|
|
695
695
|
c.el.setAttribute("x", n2(x - z / 2));
|
|
@@ -1309,6 +1309,52 @@
|
|
|
1309
1309
|
};
|
|
1310
1310
|
}
|
|
1311
1311
|
|
|
1312
|
+
// src/card-load-cdn.js
|
|
1313
|
+
var me = typeof document !== "undefined" && document.currentScript ? document.currentScript.src : "";
|
|
1314
|
+
var base = me ? me.slice(0, me.lastIndexOf("/") + 1) : "https://cdn.jsdelivr.net/npm/craft-library@0/dist/";
|
|
1315
|
+
var loadCard = () => import(base + "card.cdn.js");
|
|
1316
|
+
|
|
1317
|
+
// dist/card-mount.js
|
|
1318
|
+
var LABEL = "Credit card. Press to turn it over and see the back.";
|
|
1319
|
+
var KEYS = ["name", "number", "expiry", "cvv", "logo", "size", "entrance", "interactive"];
|
|
1320
|
+
function mountIridescentCard(host, o = {}) {
|
|
1321
|
+
let card = null, gone = false;
|
|
1322
|
+
const opts = { label: o.label || LABEL };
|
|
1323
|
+
for (const k of KEYS) if (o[k] !== void 0 && o[k] !== null) opts[k] = o[k];
|
|
1324
|
+
if (typeof opts.number === "string") opts.number = opts.number.trim().split(/\s+/);
|
|
1325
|
+
const turned = (e) => {
|
|
1326
|
+
if (o.onTurn) o.onTurn(e.detail.face);
|
|
1327
|
+
};
|
|
1328
|
+
host.addEventListener("cardturn", turned);
|
|
1329
|
+
const ready = loadCard().then((m) => {
|
|
1330
|
+
if (gone) return null;
|
|
1331
|
+
if (!opts.logo && m.LOGO) opts.logo = m.LOGO;
|
|
1332
|
+
card = m.mountCard(host, opts);
|
|
1333
|
+
return card.ready.then(() => card);
|
|
1334
|
+
});
|
|
1335
|
+
return {
|
|
1336
|
+
ready,
|
|
1337
|
+
turn: (dir) => {
|
|
1338
|
+
if (card) card.turn(dir);
|
|
1339
|
+
},
|
|
1340
|
+
show: (face) => {
|
|
1341
|
+
if (card) card.show(face);
|
|
1342
|
+
},
|
|
1343
|
+
play: () => {
|
|
1344
|
+
if (card) card.play();
|
|
1345
|
+
},
|
|
1346
|
+
get face() {
|
|
1347
|
+
return card ? card.face : "front";
|
|
1348
|
+
},
|
|
1349
|
+
destroy() {
|
|
1350
|
+
gone = true;
|
|
1351
|
+
host.removeEventListener("cardturn", turned);
|
|
1352
|
+
if (card) card.destroy();
|
|
1353
|
+
card = null;
|
|
1354
|
+
}
|
|
1355
|
+
};
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1312
1358
|
// dist/elements.js
|
|
1313
1359
|
function define(name, mount, options) {
|
|
1314
1360
|
if (typeof customElements === "undefined" || customElements.get(name)) return;
|
|
@@ -1342,7 +1388,20 @@
|
|
|
1342
1388
|
send(el, "select", { index });
|
|
1343
1389
|
}
|
|
1344
1390
|
}));
|
|
1391
|
+
define("iridescent-card", (el, o) => {
|
|
1392
|
+
css("craft-card-host", ":where(iridescent-card){display:block;position:relative;width:100%;min-width:300px;height:480px}");
|
|
1393
|
+
return mountIridescentCard(el, o);
|
|
1394
|
+
}, (el) => ({
|
|
1395
|
+
name: el.getAttribute("name") || void 0,
|
|
1396
|
+
number: el.getAttribute("number") || void 0,
|
|
1397
|
+
expiry: el.getAttribute("expiry") || void 0,
|
|
1398
|
+
cvv: el.getAttribute("cvv") || void 0,
|
|
1399
|
+
logo: el.getAttribute("logo") || void 0,
|
|
1400
|
+
size: el.getAttribute("size") ? +el.getAttribute("size") : void 0,
|
|
1401
|
+
entrance: el.getAttribute("entrance") === "false" ? false : void 0,
|
|
1402
|
+
label: el.getAttribute("label") || void 0
|
|
1403
|
+
}));
|
|
1345
1404
|
|
|
1346
1405
|
// dist/cdn.js
|
|
1347
|
-
window.CraftLibrary = { mountLike, mountMagnet, mountGlassNav };
|
|
1406
|
+
window.CraftLibrary = { mountLike, mountMagnet, mountGlassNav, mountIridescentCard };
|
|
1348
1407
|
})();
|
package/dist/elements.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
// <glass-nav>, <like-button> and <
|
|
1
|
+
// <glass-nav>, <like-button>, <magnetic-icon> and <iridescent-card>: the pieces as HTML tags, for pages without React.
|
|
2
2
|
// They render into the page itself (no shadow root), so the glass can blend with what is behind it.
|
|
3
3
|
import { mountLike } from './like.js';
|
|
4
4
|
import { mountMagnet } from './magnet.js';
|
|
5
5
|
import { mountGlassNav } from './glassnav.js';
|
|
6
|
+
import { mountIridescentCard } from './card-mount.js';
|
|
6
7
|
import { css } from './style.js';
|
|
7
8
|
|
|
8
9
|
function define(name, mount, options) {
|
|
@@ -33,4 +34,21 @@ define('glass-nav', mountGlassNav, (el) => ({
|
|
|
33
34
|
onSelect: (index) => { el.setAttribute('active', index); send(el, 'select', { index }); }
|
|
34
35
|
}));
|
|
35
36
|
|
|
36
|
-
|
|
37
|
+
// the card fills the tag: a block, 100% wide (at least 300px, so a shrink-to-fit parent can't fold it) and 480px tall
|
|
38
|
+
// unless the page sizes it (zero specificity, so any rule wins). Each turn bubbles up from inside as a "cardturn"
|
|
39
|
+
// event, with e.detail.face.
|
|
40
|
+
define('iridescent-card', (el, o) => {
|
|
41
|
+
css('craft-card-host', ':where(iridescent-card){display:block;position:relative;width:100%;min-width:300px;height:480px}');
|
|
42
|
+
return mountIridescentCard(el, o);
|
|
43
|
+
}, (el) => ({
|
|
44
|
+
name: el.getAttribute('name') || undefined,
|
|
45
|
+
number: el.getAttribute('number') || undefined,
|
|
46
|
+
expiry: el.getAttribute('expiry') || undefined,
|
|
47
|
+
cvv: el.getAttribute('cvv') || undefined,
|
|
48
|
+
logo: el.getAttribute('logo') || undefined,
|
|
49
|
+
size: el.getAttribute('size') ? +el.getAttribute('size') : undefined,
|
|
50
|
+
entrance: el.getAttribute('entrance') === 'false' ? false : undefined,
|
|
51
|
+
label: el.getAttribute('label') || undefined
|
|
52
|
+
}));
|
|
53
|
+
|
|
54
|
+
export { mountLike, mountMagnet, mountGlassNav, mountIridescentCard };
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import { createElement, useEffect, useRef } from 'react';
|
|
|
4
4
|
import { mountLike } from './like.js';
|
|
5
5
|
import { mountMagnet } from './magnet.js';
|
|
6
6
|
import { mountGlassNav } from './glassnav.js';
|
|
7
|
+
import { mountIridescentCard } from './card-mount.js';
|
|
7
8
|
|
|
8
9
|
const HOST = { display: 'inline-block', lineHeight: 0, verticalAlign: 'middle' };
|
|
9
10
|
|
|
@@ -40,4 +41,17 @@ export function GlassNav({ active = 2, labels, label, onSelect, className, style
|
|
|
40
41
|
return createElement('span', { ref: host, className, style });
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
|
|
44
|
+
// The card fills the box it renders (100% wide and `height` tall, 480px by default) and sits centred in it. The box
|
|
45
|
+
// keeps at least 300px of width, so a parent that shrinks to its content (a centring flex row) can't fold it to nothing.
|
|
46
|
+
// It loads its 3D scene (three.js) the first time it mounts. onTurn reports each turn with the face now showing.
|
|
47
|
+
export function IridescentCard({ height = 480, size, name, number, expiry, cvv, logo, entrance, label, onTurn, className, style }) {
|
|
48
|
+
const host = useRef(null), turn = useLatest(onTurn);
|
|
49
|
+
const num = Array.isArray(number) ? number.join(' ') : number;
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
const m = mountIridescentCard(host.current, { size, name, number: num, expiry, cvv, logo, entrance, label, onTurn: (f) => turn.current && turn.current(f) });
|
|
52
|
+
return () => m.destroy();
|
|
53
|
+
}, [size, name, num, expiry, cvv, logo, entrance, label]);
|
|
54
|
+
return createElement('div', { ref: host, className, style: { position: 'relative', width: '100%', minWidth: 300, height, ...style } });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export { mountLike, mountMagnet, mountGlassNav, mountIridescentCard };
|
package/dist/magnet.js
CHANGED
|
@@ -8,9 +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
|
|
12
|
-
//
|
|
13
|
-
//
|
|
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.
|
|
14
14
|
// Buttons added later: magnetize(root). To steer one without a mouse (a demo, a tour): magnetPoint(button, x, y) with
|
|
15
15
|
// a point on screen, and magnetPoint(button, null) to hand it back to the pointer.
|
|
16
16
|
(function () {
|
|
@@ -19,7 +19,7 @@ function engine() {
|
|
|
19
19
|
var PULL = 15, FALL = 1.1; // the furthest the rim reaches out, and how fast the pull drops with distance
|
|
20
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
|
|
21
21
|
var JAG = 0.18; // how deep the strained rim creases
|
|
22
|
-
var FRAY =
|
|
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
|
|
23
23
|
var DENT = 3.5; // the furthest the rim is drawn in while the pointer is over the button
|
|
24
24
|
var RATE = 90, MAXDUST = 90; // voxels shed per second at full pull, and at most at once
|
|
25
25
|
var ROOM = 46; // room for the shards and voxels on every side of the button (matches .mag-dust)
|
|
@@ -47,14 +47,14 @@ function engine() {
|
|
|
47
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);
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
// how hard the field strains the metal (0 at rest, 1 at a full pull), and how worn the
|
|
51
|
-
//
|
|
52
|
-
//
|
|
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
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) *
|
|
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
55
|
|
|
56
56
|
// the rim as one closed curve: smooth where it is calm (Catmull-Rom turned into cubic Béziers), creased into straight,
|
|
57
|
-
// trembling facets where the magnet strains it, and frayed
|
|
57
|
+
// trembling facets where the magnet strains it, and frayed into short crooked facets on either side of them
|
|
58
58
|
function rimPath(m, now) {
|
|
59
59
|
var pts = [], strain = strainOf(m), i, t, r, env, sharp, fray, n, p, a, b, c, e;
|
|
60
60
|
for (i = 0; i < N; i++) {
|
|
@@ -211,17 +211,18 @@ function engine() {
|
|
|
211
211
|
g.restore();
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
-
// the voxels falling out of the
|
|
215
|
-
// sits (and flickering at the edge of breaking)
|
|
216
|
-
// the
|
|
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
|
|
217
218
|
function chips(m, now) {
|
|
218
|
-
var t = m.t;
|
|
219
|
+
var t = m.t, f = crack(m)[0];
|
|
219
220
|
m.chips.forEach(function (c, k) {
|
|
220
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;
|
|
221
222
|
if (o < 0.02) o = 0; // closed means fully closed: no sliver left in the mask
|
|
222
223
|
if (!o && !c.open) return;
|
|
223
224
|
c.open = o;
|
|
224
|
-
z = c.s * o; r = radius(m, c.a) + c.o; x = m.cx + r * Math.cos(c.a); y = m.cy + r * Math.sin(c.a);
|
|
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);
|
|
225
226
|
c.el.setAttribute('x', n2(x - z / 2)); c.el.setAttribute('y', n2(y - z / 2));
|
|
226
227
|
c.el.setAttribute('width', n2(z)); c.el.setAttribute('height', n2(z));
|
|
227
228
|
if (o < 0.2) c.gone = false;
|
|
@@ -295,7 +296,7 @@ function engine() {
|
|
|
295
296
|
cv.className = 'mag-dust';
|
|
296
297
|
cv.setAttribute('aria-hidden', 'true');
|
|
297
298
|
el.insertBefore(cv, icon);
|
|
298
|
-
// the voxels that can fall out of the
|
|
299
|
+
// the voxels that can fall out of the rim: squares in its mask, closed until the metal wears (see chips()).
|
|
299
300
|
// Each sits at its own spot round the ring, mostly biting the rim from outside, and gives way at its own strain
|
|
300
301
|
var mask = svg.querySelector('mask'), holes = [], k, q;
|
|
301
302
|
for (k = 0; mask && k < CHIPS; k++) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "craft-library",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Craft Library by Sara Lobo: a liquid glass nav bar, a bursting heart
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Craft Library by Sara Lobo: a liquid glass nav bar, a bursting heart, a magnetic icon and an iridescent 3D card, for React or plain HTML",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"module": "./dist/index.js",
|
|
@@ -41,9 +41,15 @@
|
|
|
41
41
|
"animation",
|
|
42
42
|
"micro-interaction",
|
|
43
43
|
"navbar",
|
|
44
|
-
"like-button"
|
|
44
|
+
"like-button",
|
|
45
|
+
"3d",
|
|
46
|
+
"three",
|
|
47
|
+
"credit-card"
|
|
45
48
|
],
|
|
46
49
|
"author": "Sara Lobo",
|
|
47
50
|
"license": "MIT",
|
|
48
|
-
"homepage": "https://claude.ai/artifact/LBBWCfEHJGvfBSynV3wDuR"
|
|
51
|
+
"homepage": "https://claude.ai/artifact/LBBWCfEHJGvfBSynV3wDuR",
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"three": "0.169.0"
|
|
54
|
+
}
|
|
49
55
|
}
|