craft-library 0.6.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 +29 -3
- package/dist/cdn.js +3 -3
- package/dist/craft.js +66 -4
- package/dist/elements.js +19 -2
- package/dist/index.js +19 -1
- package/dist/panorama-assets.js +1 -1
- 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 +8 -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, the Panorama, a hero for moving pictures,
|
|
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
|
|
@@ -124,6 +124,31 @@ import { MapPin } from 'craft-library';
|
|
|
124
124
|
|
|
125
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
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
|
+
|
|
127
152
|
## Magic sphere and Sweep
|
|
128
153
|
|
|
129
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.
|
|
@@ -168,7 +193,7 @@ It is WebGL with no dependencies, so one per screen is best. Your clip and pictu
|
|
|
168
193
|
|
|
169
194
|
## Plain HTML
|
|
170
195
|
|
|
171
|
-
One script registers
|
|
196
|
+
One script registers ten tags. Same pieces, same props as attributes.
|
|
172
197
|
|
|
173
198
|
```html
|
|
174
199
|
<script src="https://cdn.jsdelivr.net/npm/craft-library@0/dist/craft.js"></script>
|
|
@@ -182,12 +207,13 @@ One script registers nine tags. Same pieces, same props as attributes.
|
|
|
182
207
|
<sweep-sphere></sweep-sphere>
|
|
183
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 -->
|
|
184
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 -->
|
|
185
211
|
```
|
|
186
212
|
|
|
187
213
|
With a bundler and no React: `import 'craft-library/elements'`.
|
|
188
214
|
|
|
189
215
|
## Motion and access
|
|
190
216
|
|
|
191
|
-
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,
|
|
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.
|
|
192
218
|
|
|
193
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, mountMapPin } from './elements.js';
|
|
4
|
-
window.CraftLibrary = { mountLike, mountMagnet, mountGlassNav, mountIridescentCard, mountPiggyBank, mountThinking, mountPanoramaReel, mountMapPin };
|
|
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, map pin · <glass-nav>, <like-button>, <magnetic-icon>, <iridescent-card>, <piggy-bank>, <magic-sphere>, <sweep-sphere>, <panorama-reel>, <map-pin> */
|
|
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");
|
|
@@ -2519,6 +2519,51 @@ void main() {
|
|
|
2519
2519
|
};
|
|
2520
2520
|
}
|
|
2521
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
|
+
|
|
2522
2567
|
// dist/elements.js
|
|
2523
2568
|
function define(name, mount, options) {
|
|
2524
2569
|
if (typeof customElements === "undefined" || customElements.get(name)) return;
|
|
@@ -2621,7 +2666,24 @@ void main() {
|
|
|
2621
2666
|
autoplay: el.getAttribute("autoplay") === "false" ? false : void 0,
|
|
2622
2667
|
fonts: el.getAttribute("fonts") === "false" ? false : void 0
|
|
2623
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
|
+
}));
|
|
2624
2686
|
|
|
2625
2687
|
// dist/cdn.js
|
|
2626
|
-
window.CraftLibrary = { mountLike, mountMagnet, mountGlassNav, mountIridescentCard, mountPiggyBank, mountThinking, mountPanoramaReel, mountMapPin };
|
|
2688
|
+
window.CraftLibrary = { mountLike, mountMagnet, mountGlassNav, mountIridescentCard, mountPiggyBank, mountThinking, mountPanoramaReel, mountMapPin, mountTokenWheel };
|
|
2627
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>, <panorama-reel> 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';
|
|
@@ -9,6 +9,7 @@ import { mountPiggyBank } from './piggy-mount.js';
|
|
|
9
9
|
import { mountThinking } from './thinking.js';
|
|
10
10
|
import { mountPanoramaReel } from './panorama-mount.js';
|
|
11
11
|
import { mountMapPin } from './pin-mount.js';
|
|
12
|
+
import { mountTokenWheel } from './tokens-mount.js';
|
|
12
13
|
import { css } from './style.js';
|
|
13
14
|
|
|
14
15
|
function define(name, mount, options) {
|
|
@@ -117,4 +118,20 @@ define('map-pin', (el, o) => {
|
|
|
117
118
|
fonts: el.getAttribute('fonts') === 'false' ? false : undefined
|
|
118
119
|
}));
|
|
119
120
|
|
|
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
|
@@ -9,6 +9,7 @@ import { mountPiggyBank } from './piggy-mount.js';
|
|
|
9
9
|
import { mountThinking } from './thinking.js';
|
|
10
10
|
import { mountPanoramaReel } from './panorama-mount.js';
|
|
11
11
|
import { mountMapPin } from './pin-mount.js';
|
|
12
|
+
import { mountTokenWheel } from './tokens-mount.js';
|
|
12
13
|
|
|
13
14
|
const HOST = { display: 'inline-block', lineHeight: 0, verticalAlign: 'middle' };
|
|
14
15
|
|
|
@@ -118,4 +119,21 @@ export function MapPin({ height = 560, labels, label, autoplay, fonts, replay, o
|
|
|
118
119
|
return createElement('div', { ref: host, className, style: { position: 'relative', width: '100%', height, ...style } });
|
|
119
120
|
}
|
|
120
121
|
|
|
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/';
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Token wheel: the mount around the library's tokens.js. tokens.js, and three.js with it, loads only when a token wheel
|
|
2
|
+
// is mounted, so the other pieces stay light.
|
|
3
|
+
import { loadTokens } from './tokens-load.js';
|
|
4
|
+
|
|
5
|
+
// host: the element the screen fills (give it a size; with the frame the phone, 390 × 844, scales to fit, and it is made
|
|
6
|
+
// for a box about 900px tall). o: { labels: { eyebrow, title, cta, initials }, frame (false: the screen fills the host,
|
|
7
|
+
// for a real phone), controls (false: no turn and replay buttons beside the phone), autoplay (false: the tokens wait for
|
|
8
|
+
// replay()), fonts (false: don't load DM Sans), gyroscope (false: never read the phone's tilt), turned (true: lay it on
|
|
9
|
+
// its side), onContinue() when the white pill is pressed }
|
|
10
|
+
export function mountTokenWheel(host, o = {}) {
|
|
11
|
+
let w = null, gone = false, wantReplay = false, wantTurn = o.turned ? true : null;
|
|
12
|
+
const opts = { labels: o.labels, frame: o.frame, controls: o.controls, autoplay: o.autoplay, fonts: o.fonts, gyroscope: o.gyroscope };
|
|
13
|
+
const cont = () => { if (o.onContinue) o.onContinue(); };
|
|
14
|
+
host.addEventListener('tokenscontinue', cont);
|
|
15
|
+
const ready = loadTokens().then((m) => {
|
|
16
|
+
if (gone) return null;
|
|
17
|
+
w = m.mountTokens(host, opts);
|
|
18
|
+
if (wantTurn != null) w.turn(wantTurn);
|
|
19
|
+
if (wantReplay) w.replay();
|
|
20
|
+
return w.ready.then(() => w);
|
|
21
|
+
});
|
|
22
|
+
return {
|
|
23
|
+
ready,
|
|
24
|
+
replay: () => { if (w) w.replay(); else wantReplay = true; },
|
|
25
|
+
turn: (side) => { if (w) return w.turn(side); wantTurn = side == null ? !wantTurn : !!side; return wantTurn; },
|
|
26
|
+
gyroscope: () => (w ? w.gyroscope() : Promise.resolve(false)), // call it from a tap: iOS asks the person first
|
|
27
|
+
get turned() { return w ? w.turned : !!wantTurn; },
|
|
28
|
+
destroy() { gone = true; host.removeEventListener('tokenscontinue', cont); if (w) w.destroy(); w = null; }
|
|
29
|
+
};
|
|
30
|
+
}
|