craft-library 0.5.0 → 0.7.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 +53 -3
- package/dist/cdn.js +3 -3
- package/dist/craft.js +127 -4
- package/dist/elements.js +35 -2
- package/dist/index.js +35 -1
- package/dist/panorama-assets.js +1 -1
- package/dist/pin-load.js +2 -0
- package/dist/pin-mount.js +30 -0
- package/dist/pin.cdn.js +977 -0
- package/dist/pin.js +977 -0
- package/dist/tokens-load.js +2 -0
- package/dist/tokens-mount.js +30 -0
- package/dist/tokens.cdn.js +604 -0
- package/dist/tokens.js +604 -0
- package/package.json +11 -3
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Sara Lobo Library
|
|
2
2
|
|
|
3
|
-
Interface pieces by [Sara Lobo](https://www.saralobodesign.com/), designed, tested and polished one detail at a time: a liquid glass nav bar, a bursting heart, a magnetic icon, an iridescent 3D card, a glass piggy bank, two "thinking" indicators, the Magic sphere and the Sweep,
|
|
3
|
+
Interface pieces by [Sara Lobo](https://www.saralobodesign.com/), designed, tested and polished one detail at a time: a liquid glass nav bar, a bursting heart, a magnetic icon, an iridescent 3D card, a glass piggy bank, two "thinking" indicators, the Magic sphere and the Sweep, the Panorama, a hero for moving pictures, the Map pin, an address picker, and the Token wheel, an onboarding screen. Each one is the exact piece from the library, with its own CSS and motion, for React 18+ or plain HTML. The only dependency is three.js, for five of the 3D pieces, and it loads only when one of them is on the page.
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
6
|
npm install craft-library
|
|
@@ -101,6 +101,54 @@ import { PiggyBank } from 'craft-library';
|
|
|
101
101
|
|
|
102
102
|
It is WebGL (three.js 0.169, which comes with the package and loads the first time it mounts), so one per screen is best. It is made for a dark background; black is best.
|
|
103
103
|
|
|
104
|
+
## Map pin
|
|
105
|
+
|
|
106
|
+
A 3D address picker: a hollow frosted-glass map pin over a dark city map in perspective. A click or a tap on a street sends the pin flying on springs to the nearest curb: it lifts, its head leans with the acceleration, it rises a touch to aim, falls under gravity and sticks its tip in, the glass vibrating about it. The map is sharp where it lands and soft further away, the streets carry their names, and a card along the bottom shows the address, with the number on the right side of the street, to confirm. With a mouse, hovering shows the address a click would give. The city and its names are invented, to show the gesture.
|
|
107
|
+
|
|
108
|
+
```jsx
|
|
109
|
+
import { MapPin } from 'craft-library';
|
|
110
|
+
|
|
111
|
+
<MapPin height={560} onConfirm={(a) => saveAddress(a)} /> // saveAddress: your own handler
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
| Prop | | |
|
|
115
|
+
|---|---|---|
|
|
116
|
+
| `height` | `number \| string` | The box it fills: 100% wide and this tall. Default `560`. |
|
|
117
|
+
| `onChange` | `(address) => void` | Each address the pin is sent to: `{ address, neighbourhood, place }`. |
|
|
118
|
+
| `onConfirm` | `(address) => void` | When Confirm is pressed, with the same address. |
|
|
119
|
+
| `labels` | `{ title, confirm, confirmed }` | The card's words. Defaults `"Delivery address"`, `"Confirm"`, `"Confirmed"`. |
|
|
120
|
+
| `replay` | `any` | Change it (a counter, say) to fly the pin off and back in. |
|
|
121
|
+
| `autoplay` | `boolean` | `false` keeps the pin out until `replay` changes. Default `true`. |
|
|
122
|
+
| `fonts` | `boolean` | `false` doesn't load DM Sans from Google Fonts. Default `true`. |
|
|
123
|
+
| `label` | `string` | The map's accessible name. |
|
|
124
|
+
|
|
125
|
+
It is WebGL (three.js 0.169, which comes with the package and loads the first time it mounts), so one per screen is best. It paints its own dark ground.
|
|
126
|
+
|
|
127
|
+
## Token wheel
|
|
128
|
+
|
|
129
|
+
An account onboarding screen in 3D: eight silver tokens (Pix, card, Bitcoin, insurance, investments, exchange, cashback, boleto) turn slowly on a wheel, and the one at the top comes forward and makes a full turn to show what it is. Turn the phone on its side and the tokens let go: they fall, roll and pile up against the edge of the screen with rigid-body physics, a metal bounce and a wobble at every knock; stand it up and, once they rest, they float back into the wheel. The screen sits in a phone that scales to fit the box at every angle, with two round buttons beside it: turn the phone, replay.
|
|
130
|
+
|
|
131
|
+
```jsx
|
|
132
|
+
import { TokenWheel } from 'craft-library';
|
|
133
|
+
|
|
134
|
+
<TokenWheel height={900} onContinue={() => goToNextStep()} /> // goToNextStep: your own handler
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
| Prop | | |
|
|
138
|
+
|---|---|---|
|
|
139
|
+
| `height` | `number \| string` | The box it fills: 100% wide and this tall. Default `900`. |
|
|
140
|
+
| `labels` | `{ eyebrow, title, cta, initials }` | The screen's words. Defaults `"Account · BRL"`, `"One account for everything"`, `"Continue"`, `"SL"`. |
|
|
141
|
+
| `onContinue` | `() => void` | When the white pill is pressed. |
|
|
142
|
+
| `turned` | `boolean` | `true` lays the phone on its side, `false` stands it up. |
|
|
143
|
+
| `frame` | `boolean` | `false` fills the box with the screen itself, for a real phone (give it `100svh`): the phone's own tilt then moves the tokens (on iOS, after a tap on the wheel). Default `true`. |
|
|
144
|
+
| `controls` | `boolean` | `false` hides the two round buttons. Default `true`. |
|
|
145
|
+
| `replay` | `any` | Change it (a counter, say) to play the opening again. |
|
|
146
|
+
| `autoplay` | `boolean` | `false` keeps the tokens out until `replay` changes. Default `true`. |
|
|
147
|
+
| `gyroscope` | `boolean` | `false` never reads the phone's tilt. Default `true`. |
|
|
148
|
+
| `fonts` | `boolean` | `false` doesn't load DM Sans from Google Fonts. Default `true`. |
|
|
149
|
+
|
|
150
|
+
It is WebGL (three.js 0.169, which comes with the package and loads the first time it mounts), so one per screen is best. The screen is true black; put it on a dark page.
|
|
151
|
+
|
|
104
152
|
## Magic sphere and Sweep
|
|
105
153
|
|
|
106
154
|
Two "thinking" indicators for the wait while an answer is being written: a ball of dots, drawn on a canvas, with the word beside it. The **Magic sphere** is a Rubik's cube on a ball, one grey tone per face: its slices turn and scramble the pieces, retrace their steps until every piece is home, and the ball lights up for a moment while a light passes through the word. In the **Sweep**, a wave crosses a dotted globe from left to right, lifting the dots it passes, runs on through the word at the same speed, and rests.
|
|
@@ -145,7 +193,7 @@ It is WebGL with no dependencies, so one per screen is best. Your clip and pictu
|
|
|
145
193
|
|
|
146
194
|
## Plain HTML
|
|
147
195
|
|
|
148
|
-
One script registers
|
|
196
|
+
One script registers ten tags. Same pieces, same props as attributes.
|
|
149
197
|
|
|
150
198
|
```html
|
|
151
199
|
<script src="https://cdn.jsdelivr.net/npm/craft-library@0/dist/craft.js"></script>
|
|
@@ -158,12 +206,14 @@ One script registers eight tags. Same pieces, same props as attributes.
|
|
|
158
206
|
<magic-sphere></magic-sphere> <!-- label="Thinking" (default; label="" for the ball alone), size="32" -->
|
|
159
207
|
<sweep-sphere></sweep-sphere>
|
|
160
208
|
<panorama-reel heading="Lorem ipsum"></panorama-reel> <!-- text, text-italic; video and stills as JSON; el.replay(); 100% wide, 640px tall unless you size it -->
|
|
209
|
+
<map-pin></map-pin> <!-- "pinchange" and "pinconfirm" events: e.detail { address, neighbourhood, place }; label-title, label-confirm, label-confirmed; el.replay(); 100% wide, 560px tall unless you size it -->
|
|
210
|
+
<token-wheel></token-wheel> <!-- "tokenscontinue" event; label-eyebrow, label-title, label-cta, initials; frame="false", controls="false", turned; el.turn(true|false), el.replay(); 100% wide, 900px tall unless you size it -->
|
|
161
211
|
```
|
|
162
212
|
|
|
163
213
|
With a bundler and no React: `import 'craft-library/elements'`.
|
|
164
214
|
|
|
165
215
|
## Motion and access
|
|
166
216
|
|
|
167
|
-
Every item is a real `<button>` with an accessible name; the nav marks the chosen one with `aria-current` and moves with the arrow keys. The card is a `role="button"` that Enter, Space and the arrow keys turn over; the piggy bank is a
|
|
217
|
+
Every item is a real `<button>` with an accessible name; the nav marks the chosen one with `aria-current` and moves with the arrow keys. The card is a `role="button"` that Enter, Space and the arrow keys turn over; the piggy bank and the map pin's map are labelled images; the pin's address is announced as it changes and Confirm is a real button. With `prefers-reduced-motion`, the heart only changes colour, the nav only slides, the magnet stays still, the card swaps faces with a fade, with no entrance and no breathing, the piggy bank stays in place while the coins appear inside it and its eyes close, the thinking balls hold still and only breathe, the panorama skips its opening and starts settled, the map pin appears in place without its flight, and the token wheel starts formed and still. The token wheel's wheel is a `role="button"` that Enter and Space replay, its title is a heading and its round buttons are real buttons with names. The panorama's title is a heading; its screens and cursor are decorative.
|
|
168
218
|
|
|
169
219
|
MIT · [Sara Lobo Library](https://saralobolibrary.com)
|
package/dist/cdn.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// the one-file build for a <script> tag: registers the
|
|
1
|
+
// the one-file build for a <script> tag: registers the ten tags and exposes the mount functions (the 3D pieces' scenes
|
|
2
2
|
// are fetched from next to this file the first time one is mounted, and so are the Panorama's clip and pictures)
|
|
3
|
-
import { mountLike, mountMagnet, mountGlassNav, mountIridescentCard, mountPiggyBank, mountThinking, mountPanoramaReel } from './elements.js';
|
|
4
|
-
window.CraftLibrary = { mountLike, mountMagnet, mountGlassNav, mountIridescentCard, mountPiggyBank, mountThinking, mountPanoramaReel };
|
|
3
|
+
import { mountLike, mountMagnet, mountGlassNav, mountIridescentCard, mountPiggyBank, mountThinking, mountPanoramaReel, mountMapPin, mountTokenWheel } from './elements.js';
|
|
4
|
+
window.CraftLibrary = { mountLike, mountMagnet, mountGlassNav, mountIridescentCard, mountPiggyBank, mountThinking, mountPanoramaReel, mountMapPin, mountTokenWheel };
|
package/dist/craft.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* Sara Lobo Library · liquid glass nav bar, bursting heart, magnetic icon, iridescent card, glass piggy bank, magic sphere, sweep, panorama · <glass-nav>, <like-button>, <magnetic-icon>, <iridescent-card>, <piggy-bank>, <magic-sphere>, <sweep-sphere>, <panorama-reel> */
|
|
1
|
+
/* Sara Lobo Library · liquid glass nav bar, bursting heart, magnetic icon, iridescent card, glass piggy bank, magic sphere, sweep, panorama, map pin, token wheel · <glass-nav>, <like-button>, <magnetic-icon>, <iridescent-card>, <piggy-bank>, <magic-sphere>, <sweep-sphere>, <panorama-reel>, <map-pin>, <token-wheel> */
|
|
2
2
|
(() => {
|
|
3
3
|
// dist/style.js
|
|
4
4
|
var done = /* @__PURE__ */ new Set();
|
|
@@ -2010,10 +2010,10 @@ void main() {
|
|
|
2010
2010
|
for (let i = 0; i < MAX; i++) idx.set([i * 4, i * 4 + 1, i * 4 + 2, i * 4, i * 4 + 2, i * 4 + 3], i * 6);
|
|
2011
2011
|
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, gl.createBuffer());
|
|
2012
2012
|
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, idx, gl.STATIC_DRAW);
|
|
2013
|
-
[["aPos", 3, 0], ["aUv", 2, 3], ["aRect", 4, 5], ["aMisc", 4, 9], ["aPan", 2, 13], ["aLab", 2, 15], ["aGlow", 1, 17]].forEach(([name, size,
|
|
2013
|
+
[["aPos", 3, 0], ["aUv", 2, 3], ["aRect", 4, 5], ["aMisc", 4, 9], ["aPan", 2, 13], ["aLab", 2, 15], ["aGlow", 1, 17]].forEach(([name, size, off2]) => {
|
|
2014
2014
|
const loc = gl.getAttribLocation(prog, name);
|
|
2015
2015
|
gl.enableVertexAttribArray(loc);
|
|
2016
|
-
gl.vertexAttribPointer(loc, size, gl.FLOAT, false, F * 4,
|
|
2016
|
+
gl.vertexAttribPointer(loc, size, gl.FLOAT, false, F * 4, off2 * 4);
|
|
2017
2017
|
});
|
|
2018
2018
|
const U = (n2) => gl.getUniformLocation(prog, n2);
|
|
2019
2019
|
const uHalf = U("uHalf"), uP = U("uP"), uLab = U("uLab"), uView = U("uView"), uDpr = U("uDpr"), uColor = U("uColor");
|
|
@@ -2469,6 +2469,101 @@ void main() {
|
|
|
2469
2469
|
return mountPanorama(host, opts);
|
|
2470
2470
|
}
|
|
2471
2471
|
|
|
2472
|
+
// src/pin-load-cdn.js
|
|
2473
|
+
var me4 = typeof document !== "undefined" && document.currentScript ? document.currentScript.src : "";
|
|
2474
|
+
var base3 = me4 ? me4.slice(0, me4.lastIndexOf("/") + 1) : "https://cdn.jsdelivr.net/npm/craft-library@0/dist/";
|
|
2475
|
+
var loadPin = () => import(base3 + "pin.cdn.js");
|
|
2476
|
+
|
|
2477
|
+
// dist/pin-mount.js
|
|
2478
|
+
function mountMapPin(host, o = {}) {
|
|
2479
|
+
let pin = null, gone = false, wantReplay = false;
|
|
2480
|
+
const opts = { labels: o.labels, label: o.label, manual: o.manual };
|
|
2481
|
+
if (o.autoplay === false) opts.autoplay = false;
|
|
2482
|
+
if (o.fonts === false) opts.fonts = false;
|
|
2483
|
+
const change = (e) => {
|
|
2484
|
+
if (o.onChange) o.onChange(e.detail);
|
|
2485
|
+
};
|
|
2486
|
+
const confirm = (e) => {
|
|
2487
|
+
if (o.onConfirm) o.onConfirm(e.detail);
|
|
2488
|
+
};
|
|
2489
|
+
host.addEventListener("pinchange", change);
|
|
2490
|
+
host.addEventListener("pinconfirm", confirm);
|
|
2491
|
+
const ready = loadPin().then((m) => {
|
|
2492
|
+
if (gone) return null;
|
|
2493
|
+
pin = m.mountPin(host, opts);
|
|
2494
|
+
if (wantReplay) pin.replay();
|
|
2495
|
+
return pin.ready.then(() => pin);
|
|
2496
|
+
});
|
|
2497
|
+
return {
|
|
2498
|
+
ready,
|
|
2499
|
+
replay: () => {
|
|
2500
|
+
if (pin) pin.replay();
|
|
2501
|
+
else wantReplay = true;
|
|
2502
|
+
},
|
|
2503
|
+
confirm: () => {
|
|
2504
|
+
if (pin) pin.confirm();
|
|
2505
|
+
},
|
|
2506
|
+
tick: (dt, now) => {
|
|
2507
|
+
if (pin) pin.tick(dt, now);
|
|
2508
|
+
},
|
|
2509
|
+
get address() {
|
|
2510
|
+
return pin ? pin.address : null;
|
|
2511
|
+
},
|
|
2512
|
+
destroy() {
|
|
2513
|
+
gone = true;
|
|
2514
|
+
host.removeEventListener("pinchange", change);
|
|
2515
|
+
host.removeEventListener("pinconfirm", confirm);
|
|
2516
|
+
if (pin) pin.destroy();
|
|
2517
|
+
pin = null;
|
|
2518
|
+
}
|
|
2519
|
+
};
|
|
2520
|
+
}
|
|
2521
|
+
|
|
2522
|
+
// src/tokens-load-cdn.js
|
|
2523
|
+
var me5 = typeof document !== "undefined" && document.currentScript ? document.currentScript.src : "";
|
|
2524
|
+
var base4 = me5 ? me5.slice(0, me5.lastIndexOf("/") + 1) : "https://cdn.jsdelivr.net/npm/craft-library@0/dist/";
|
|
2525
|
+
var loadTokens = () => import(base4 + "tokens.cdn.js");
|
|
2526
|
+
|
|
2527
|
+
// dist/tokens-mount.js
|
|
2528
|
+
function mountTokenWheel(host, o = {}) {
|
|
2529
|
+
let w = null, gone = false, wantReplay = false, wantTurn = o.turned ? true : null;
|
|
2530
|
+
const opts = { labels: o.labels, frame: o.frame, controls: o.controls, autoplay: o.autoplay, fonts: o.fonts, gyroscope: o.gyroscope };
|
|
2531
|
+
const cont = () => {
|
|
2532
|
+
if (o.onContinue) o.onContinue();
|
|
2533
|
+
};
|
|
2534
|
+
host.addEventListener("tokenscontinue", cont);
|
|
2535
|
+
const ready = loadTokens().then((m) => {
|
|
2536
|
+
if (gone) return null;
|
|
2537
|
+
w = m.mountTokens(host, opts);
|
|
2538
|
+
if (wantTurn != null) w.turn(wantTurn);
|
|
2539
|
+
if (wantReplay) w.replay();
|
|
2540
|
+
return w.ready.then(() => w);
|
|
2541
|
+
});
|
|
2542
|
+
return {
|
|
2543
|
+
ready,
|
|
2544
|
+
replay: () => {
|
|
2545
|
+
if (w) w.replay();
|
|
2546
|
+
else wantReplay = true;
|
|
2547
|
+
},
|
|
2548
|
+
turn: (side) => {
|
|
2549
|
+
if (w) return w.turn(side);
|
|
2550
|
+
wantTurn = side == null ? !wantTurn : !!side;
|
|
2551
|
+
return wantTurn;
|
|
2552
|
+
},
|
|
2553
|
+
gyroscope: () => w ? w.gyroscope() : Promise.resolve(false),
|
|
2554
|
+
// call it from a tap: iOS asks the person first
|
|
2555
|
+
get turned() {
|
|
2556
|
+
return w ? w.turned : !!wantTurn;
|
|
2557
|
+
},
|
|
2558
|
+
destroy() {
|
|
2559
|
+
gone = true;
|
|
2560
|
+
host.removeEventListener("tokenscontinue", cont);
|
|
2561
|
+
if (w) w.destroy();
|
|
2562
|
+
w = null;
|
|
2563
|
+
}
|
|
2564
|
+
};
|
|
2565
|
+
}
|
|
2566
|
+
|
|
2472
2567
|
// dist/elements.js
|
|
2473
2568
|
function define(name, mount, options) {
|
|
2474
2569
|
if (typeof customElements === "undefined" || customElements.get(name)) return;
|
|
@@ -2560,7 +2655,35 @@ void main() {
|
|
|
2560
2655
|
cursor: el.getAttribute("cursor") === "false" ? false : void 0,
|
|
2561
2656
|
fonts: el.getAttribute("fonts") === "false" ? false : void 0
|
|
2562
2657
|
}));
|
|
2658
|
+
define("map-pin", (el, o) => {
|
|
2659
|
+
css("craft-pin-host", ":where(map-pin){display:block;position:relative;width:100%;height:560px}");
|
|
2660
|
+
const m = mountMapPin(el, o);
|
|
2661
|
+
el.replay = m.replay;
|
|
2662
|
+
return m;
|
|
2663
|
+
}, (el) => ({
|
|
2664
|
+
labels: { title: el.getAttribute("label-title") ?? void 0, confirm: el.getAttribute("label-confirm") ?? void 0, confirmed: el.getAttribute("label-confirmed") ?? void 0 },
|
|
2665
|
+
label: el.getAttribute("label") || void 0,
|
|
2666
|
+
autoplay: el.getAttribute("autoplay") === "false" ? false : void 0,
|
|
2667
|
+
fonts: el.getAttribute("fonts") === "false" ? false : void 0
|
|
2668
|
+
}));
|
|
2669
|
+
var off = (el, name) => el.getAttribute(name) === "false" ? false : void 0;
|
|
2670
|
+
define("token-wheel", (el, o) => {
|
|
2671
|
+
css("craft-tokens-host", ":where(token-wheel){display:block;position:relative;width:100%;height:900px}");
|
|
2672
|
+
const m = mountTokenWheel(el, o);
|
|
2673
|
+
el.replay = m.replay;
|
|
2674
|
+
el.turn = m.turn;
|
|
2675
|
+
el.gyroscope = m.gyroscope;
|
|
2676
|
+
return m;
|
|
2677
|
+
}, (el) => ({
|
|
2678
|
+
labels: { eyebrow: el.getAttribute("label-eyebrow") ?? void 0, title: el.getAttribute("label-title") ?? void 0, cta: el.getAttribute("label-cta") ?? void 0, initials: el.getAttribute("initials") ?? void 0 },
|
|
2679
|
+
frame: off(el, "frame"),
|
|
2680
|
+
controls: off(el, "controls"),
|
|
2681
|
+
autoplay: off(el, "autoplay"),
|
|
2682
|
+
fonts: off(el, "fonts"),
|
|
2683
|
+
gyroscope: off(el, "gyroscope"),
|
|
2684
|
+
turned: el.hasAttribute("turned") && el.getAttribute("turned") !== "false"
|
|
2685
|
+
}));
|
|
2563
2686
|
|
|
2564
2687
|
// dist/cdn.js
|
|
2565
|
-
window.CraftLibrary = { mountLike, mountMagnet, mountGlassNav, mountIridescentCard, mountPiggyBank, mountThinking, mountPanoramaReel };
|
|
2688
|
+
window.CraftLibrary = { mountLike, mountMagnet, mountGlassNav, mountIridescentCard, mountPiggyBank, mountThinking, mountPanoramaReel, mountMapPin, mountTokenWheel };
|
|
2566
2689
|
})();
|
package/dist/elements.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// <glass-nav>, <like-button>, <magnetic-icon>, <iridescent-card>, <piggy-bank>, <magic-sphere>, <sweep-sphere> and <
|
|
1
|
+
// <glass-nav>, <like-button>, <magnetic-icon>, <iridescent-card>, <piggy-bank>, <magic-sphere>, <sweep-sphere>, <panorama-reel>, <map-pin> and <token-wheel>: the
|
|
2
2
|
// pieces as HTML tags, for pages without React. They render into the page itself (no shadow root), so the glass can
|
|
3
3
|
// blend with what is behind it.
|
|
4
4
|
import { mountLike } from './like.js';
|
|
@@ -8,6 +8,8 @@ import { mountIridescentCard } from './card-mount.js';
|
|
|
8
8
|
import { mountPiggyBank } from './piggy-mount.js';
|
|
9
9
|
import { mountThinking } from './thinking.js';
|
|
10
10
|
import { mountPanoramaReel } from './panorama-mount.js';
|
|
11
|
+
import { mountMapPin } from './pin-mount.js';
|
|
12
|
+
import { mountTokenWheel } from './tokens-mount.js';
|
|
11
13
|
import { css } from './style.js';
|
|
12
14
|
|
|
13
15
|
function define(name, mount, options) {
|
|
@@ -101,4 +103,35 @@ define('panorama-reel', (el, o) => {
|
|
|
101
103
|
fonts: el.getAttribute('fonts') === 'false' ? false : undefined
|
|
102
104
|
}));
|
|
103
105
|
|
|
104
|
-
|
|
106
|
+
// the map pin fills the tag: a block, 100% wide and 560px tall unless the page sizes it. Each address the pin is sent to
|
|
107
|
+
// fires "pinchange" and a confirmation "pinconfirm", both with e.detail { address, neighbourhood, place }. The words:
|
|
108
|
+
// label-title, label-confirm, label-confirmed. el.replay() flies the pin off and back in.
|
|
109
|
+
define('map-pin', (el, o) => {
|
|
110
|
+
css('craft-pin-host', ':where(map-pin){display:block;position:relative;width:100%;height:560px}');
|
|
111
|
+
const m = mountMapPin(el, o);
|
|
112
|
+
el.replay = m.replay;
|
|
113
|
+
return m;
|
|
114
|
+
}, (el) => ({
|
|
115
|
+
labels: { title: el.getAttribute('label-title') ?? undefined, confirm: el.getAttribute('label-confirm') ?? undefined, confirmed: el.getAttribute('label-confirmed') ?? undefined },
|
|
116
|
+
label: el.getAttribute('label') || undefined,
|
|
117
|
+
autoplay: el.getAttribute('autoplay') === 'false' ? false : undefined,
|
|
118
|
+
fonts: el.getAttribute('fonts') === 'false' ? false : undefined
|
|
119
|
+
}));
|
|
120
|
+
|
|
121
|
+
// the token wheel fills the tag: a block, 100% wide and 900px tall unless the page sizes it. Its white pill fires
|
|
122
|
+
// "tokenscontinue". The words: label-eyebrow, label-title, label-cta, initials. frame="false" fills the tag with the
|
|
123
|
+
// screen (a real phone), controls="false" hides the round buttons, turned lays it on its side; from code, el.turn(true)
|
|
124
|
+
// lays it down, el.turn(false) stands it up and el.replay() plays the opening again.
|
|
125
|
+
const off = (el, name) => (el.getAttribute(name) === 'false' ? false : undefined);
|
|
126
|
+
define('token-wheel', (el, o) => {
|
|
127
|
+
css('craft-tokens-host', ':where(token-wheel){display:block;position:relative;width:100%;height:900px}');
|
|
128
|
+
const m = mountTokenWheel(el, o);
|
|
129
|
+
el.replay = m.replay; el.turn = m.turn; el.gyroscope = m.gyroscope;
|
|
130
|
+
return m;
|
|
131
|
+
}, (el) => ({
|
|
132
|
+
labels: { eyebrow: el.getAttribute('label-eyebrow') ?? undefined, title: el.getAttribute('label-title') ?? undefined, cta: el.getAttribute('label-cta') ?? undefined, initials: el.getAttribute('initials') ?? undefined },
|
|
133
|
+
frame: off(el, 'frame'), controls: off(el, 'controls'), autoplay: off(el, 'autoplay'), fonts: off(el, 'fonts'), gyroscope: off(el, 'gyroscope'),
|
|
134
|
+
turned: el.hasAttribute('turned') && el.getAttribute('turned') !== 'false'
|
|
135
|
+
}));
|
|
136
|
+
|
|
137
|
+
export { mountLike, mountMagnet, mountGlassNav, mountIridescentCard, mountPiggyBank, mountThinking, mountPanoramaReel, mountMapPin, mountTokenWheel };
|
package/dist/index.js
CHANGED
|
@@ -8,6 +8,8 @@ import { mountIridescentCard } from './card-mount.js';
|
|
|
8
8
|
import { mountPiggyBank } from './piggy-mount.js';
|
|
9
9
|
import { mountThinking } from './thinking.js';
|
|
10
10
|
import { mountPanoramaReel } from './panorama-mount.js';
|
|
11
|
+
import { mountMapPin } from './pin-mount.js';
|
|
12
|
+
import { mountTokenWheel } from './tokens-mount.js';
|
|
11
13
|
|
|
12
14
|
const HOST = { display: 'inline-block', lineHeight: 0, verticalAlign: 'middle' };
|
|
13
15
|
|
|
@@ -102,4 +104,36 @@ export function PanoramaReel({ height = 640, title, text, textItalic, video, sti
|
|
|
102
104
|
return createElement('div', { ref: host, className, style: { position: 'relative', width: '100%', height, ...style } });
|
|
103
105
|
}
|
|
104
106
|
|
|
105
|
-
|
|
107
|
+
// The map pin fills the box it renders (100% wide and `height` tall, 560px by default) with a city map and a glass pin:
|
|
108
|
+
// a click on a street sends the pin to its curb, and the card under the map confirms the address. onChange and onConfirm
|
|
109
|
+
// get { address, neighbourhood, place }. labels: { title, confirm, confirmed }, the card's words. replay: change it (a
|
|
110
|
+
// counter) to fly the pin off and back in. It loads its 3D scene (three.js) the first time it mounts.
|
|
111
|
+
export function MapPin({ height = 560, labels, label, autoplay, fonts, replay, onChange, onConfirm, className, style }) {
|
|
112
|
+
const host = useRef(null), pin = useRef(null), last = useRef(replay), change = useLatest(onChange), confirm = useLatest(onConfirm);
|
|
113
|
+
const key = JSON.stringify([labels, label, autoplay, fonts]);
|
|
114
|
+
useEffect(() => {
|
|
115
|
+
const m = pin.current = mountMapPin(host.current, { labels, label, autoplay, fonts, onChange: (a) => change.current && change.current(a), onConfirm: (a) => confirm.current && confirm.current(a) });
|
|
116
|
+
return () => { m.destroy(); pin.current = null; };
|
|
117
|
+
}, [key]);
|
|
118
|
+
useEffect(() => { if (last.current === replay) return; last.current = replay; if (pin.current) pin.current.replay(); }, [replay]);
|
|
119
|
+
return createElement('div', { ref: host, className, style: { position: 'relative', width: '100%', height, ...style } });
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// The token wheel fills the box it renders (100% wide and `height` tall, 900px by default): an account onboarding screen
|
|
123
|
+
// whose silver tokens turn on a wheel. With the frame (the default) the screen sits in a phone that scales to fit the box,
|
|
124
|
+
// with two round buttons beside it, turn it on its side and replay; turned lays it down from code, and the tokens fall
|
|
125
|
+
// and pile up against the edge. frame={false} fills the box with the screen itself, for a real phone, where the phone's
|
|
126
|
+
// own tilt moves the tokens. onContinue: the white pill. It loads its 3D scene (three.js) the first time it mounts.
|
|
127
|
+
export function TokenWheel({ height = 900, labels, frame, controls, autoplay, fonts, gyroscope, turned, replay, onContinue, className, style }) {
|
|
128
|
+
const host = useRef(null), wheel = useRef(null), last = useRef(replay), cont = useLatest(onContinue);
|
|
129
|
+
const key = JSON.stringify([labels, frame, controls, autoplay, fonts, gyroscope]);
|
|
130
|
+
useEffect(() => {
|
|
131
|
+
const m = wheel.current = mountTokenWheel(host.current, { labels, frame, controls, autoplay, fonts, gyroscope, turned, onContinue: () => cont.current && cont.current() });
|
|
132
|
+
return () => { m.destroy(); wheel.current = null; };
|
|
133
|
+
}, [key]);
|
|
134
|
+
useEffect(() => { const m = wheel.current; if (m && turned != null && m.turned !== !!turned) m.turn(!!turned); }, [turned]);
|
|
135
|
+
useEffect(() => { if (last.current === replay) return; last.current = replay; if (wheel.current) wheel.current.replay(); }, [replay]);
|
|
136
|
+
return createElement('div', { ref: host, className, style: { position: 'relative', width: '100%', height, ...style } });
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export { mountLike, mountMagnet, mountGlassNav, mountIridescentCard, mountPiggyBank, mountThinking, mountPanoramaReel, mountMapPin, mountTokenWheel };
|
package/dist/panorama-assets.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// where the Panorama's own clip and pictures are: jsDelivr, at this version of the package
|
|
2
|
-
export const ASSETS = 'https://cdn.jsdelivr.net/npm/craft-library@0.
|
|
2
|
+
export const ASSETS = 'https://cdn.jsdelivr.net/npm/craft-library@0.7.0/dist/panorama/';
|
package/dist/pin-load.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Map pin: the mount around the library's pin.js. pin.js, and three.js with it, loads only when a map pin is mounted,
|
|
2
|
+
// so the other pieces stay light.
|
|
3
|
+
import { loadPin } from './pin-load.js';
|
|
4
|
+
|
|
5
|
+
// host: the element the map fills (give it a size; it is made for about 560 × 560). o: { labels: { title, confirm,
|
|
6
|
+
// confirmed }, label (the map's accessible name), autoplay (false: the pin waits for replay()), fonts (false: don't load
|
|
7
|
+
// DM Sans), onChange(address), onConfirm(address) }; address: { address, neighbourhood, place }
|
|
8
|
+
export function mountMapPin(host, o = {}) {
|
|
9
|
+
let pin = null, gone = false, wantReplay = false;
|
|
10
|
+
const opts = { labels: o.labels, label: o.label, manual: o.manual };
|
|
11
|
+
if (o.autoplay === false) opts.autoplay = false;
|
|
12
|
+
if (o.fonts === false) opts.fonts = false;
|
|
13
|
+
const change = (e) => { if (o.onChange) o.onChange(e.detail); };
|
|
14
|
+
const confirm = (e) => { if (o.onConfirm) o.onConfirm(e.detail); };
|
|
15
|
+
host.addEventListener('pinchange', change); host.addEventListener('pinconfirm', confirm);
|
|
16
|
+
const ready = loadPin().then((m) => {
|
|
17
|
+
if (gone) return null;
|
|
18
|
+
pin = m.mountPin(host, opts);
|
|
19
|
+
if (wantReplay) pin.replay();
|
|
20
|
+
return pin.ready.then(() => pin);
|
|
21
|
+
});
|
|
22
|
+
return {
|
|
23
|
+
ready,
|
|
24
|
+
replay: () => { if (pin) pin.replay(); else wantReplay = true; },
|
|
25
|
+
confirm: () => { if (pin) pin.confirm(); },
|
|
26
|
+
tick: (dt, now) => { if (pin) pin.tick(dt, now); },
|
|
27
|
+
get address() { return pin ? pin.address : null; },
|
|
28
|
+
destroy() { gone = true; host.removeEventListener('pinchange', change); host.removeEventListener('pinconfirm', confirm); if (pin) pin.destroy(); pin = null; }
|
|
29
|
+
};
|
|
30
|
+
}
|