craft-library 0.4.0 → 0.5.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/dist/elements.js CHANGED
@@ -1,4 +1,4 @@
1
- // <glass-nav>, <like-button>, <magnetic-icon>, <iridescent-card>, <piggy-bank>, <magic-sphere> and <sweep-sphere>: the
1
+ // <glass-nav>, <like-button>, <magnetic-icon>, <iridescent-card>, <piggy-bank>, <magic-sphere>, <sweep-sphere> and <panorama-reel>: 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';
@@ -7,6 +7,7 @@ import { mountGlassNav } from './glassnav.js';
7
7
  import { mountIridescentCard } from './card-mount.js';
8
8
  import { mountPiggyBank } from './piggy-mount.js';
9
9
  import { mountThinking } from './thinking.js';
10
+ import { mountPanoramaReel } from './panorama-mount.js';
10
11
  import { css } from './style.js';
11
12
 
12
13
  function define(name, mount, options) {
@@ -80,4 +81,24 @@ const thinkingOptions = (el) => ({
80
81
  define('magic-sphere', thinkingTag('sphere'), thinkingOptions);
81
82
  define('sweep-sphere', thinkingTag('sweep'), thinkingOptions);
82
83
 
83
- export { mountLike, mountMagnet, mountGlassNav, mountIridescentCard, mountPiggyBank, mountThinking };
84
+ // the panorama fills the tag: a block, 100% wide and 640px tall unless the page sizes it. heading="…" is its title (a
85
+ // title attribute would also show as a tooltip); video and stills take JSON. el.replay() plays the opening again.
86
+ const json = (s) => { if (!s) return undefined; try { return JSON.parse(s); } catch (e) { console.warn('panorama-reel: not JSON', s); return undefined; } };
87
+ define('panorama-reel', (el, o) => {
88
+ css('craft-panorama-host', ':where(panorama-reel){display:block;position:relative;width:100%;height:640px}');
89
+ const m = mountPanoramaReel(el, o);
90
+ el.replay = m.replay;
91
+ return m;
92
+ }, (el) => ({
93
+ title: el.getAttribute('heading') ?? undefined,
94
+ text: el.getAttribute('text') ?? undefined,
95
+ textItalic: el.getAttribute('text-italic') ?? undefined,
96
+ video: json(el.getAttribute('video')),
97
+ stills: json(el.getAttribute('stills')),
98
+ labels: el.getAttribute('labels') === 'false' ? false : undefined,
99
+ intro: el.getAttribute('intro') === 'false' ? false : undefined,
100
+ cursor: el.getAttribute('cursor') === 'false' ? false : undefined,
101
+ fonts: el.getAttribute('fonts') === 'false' ? false : undefined
102
+ }));
103
+
104
+ export { mountLike, mountMagnet, mountGlassNav, mountIridescentCard, mountPiggyBank, mountThinking, mountPanoramaReel };
package/dist/glassnav.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // Liquid glass nav bar · Craft Library, by Sara Lobo. The bar's CSS, markup and engine from the library, unchanged,
2
2
  // around a mount.
3
3
  import { css } from './style.js';
4
- const CSS = "/* Liquid glass nav bar · Craft Library, by Sara Lobo\n Figma \"Liquid Glass Button - Dark\", 291×60, for a dark app. Paint order follows the Figma layer: background blur →\n fills → lenses → capsule and icons → inner shadows → strokes. The bar stays out of any stacking context (no transform,\n opacity or isolation on it), so every blend mode mixes with what is behind it: keep it on a solid background. */\n.lgn-wrap{position:relative;display:inline-block}\n.lgn{position:relative;display:block;width:291px;height:60px;border-radius:999px;overflow:hidden;touch-action:pan-y;-webkit-user-select:none;user-select:none}\n.lgn > *{position:absolute}\n.lgn-fx{inset:0;border-radius:inherit;pointer-events:none}\n\n/* fills, bottom to top: a soft light from the top left, a grey veil and a near-white linear burn */\n.lgn-blur{-webkit-backdrop-filter:blur(15.96px);backdrop-filter:blur(15.96px)}\n.lgn-sheen{mix-blend-mode:plus-lighter;opacity:.1;background:url(\"data:image/svg+xml;utf8,<svg viewBox='0 0 291 60' xmlns='http://www.w3.org/2000/svg' preserveAspectRatio='none'><defs><radialGradient id='g' gradientUnits='userSpaceOnUse' cx='0' cy='0' r='10' gradientTransform='matrix(16.645 3.2563 -15.793 3.4319 38.852 9.8387)'><stop stop-color='%23fff'/><stop offset='1' stop-color='%23fff' stop-opacity='0'/></radialGradient></defs><rect width='291' height='60' fill='url(%23g)'/></svg>\") 0 0/100% 100%}\n.lgn-veil{background:rgba(51,51,51,.2)}\n.lgn-burn{background:#E6E6E6;mix-blend-mode:color-burn}\n\n/* lenses: four nested capsules, each a little smaller, each blurring what is behind it a little less */\n.lgn-lens{border-radius:999px;background:rgba(255,255,255,.01);pointer-events:none}\n.lgn-lens:nth-of-type(1){inset:0 .09px 0 -.21px;-webkit-backdrop-filter:blur(10.64px);backdrop-filter:blur(10.64px)}\n.lgn-lens:nth-of-type(2){inset:.64px .52px .64px .23px;-webkit-backdrop-filter:blur(5.32px);backdrop-filter:blur(5.32px)}\n.lgn-lens:nth-of-type(3){inset:1.91px 1.4px 1.91px 1.11px;-webkit-backdrop-filter:blur(2.66px);backdrop-filter:blur(2.66px)}\n.lgn-lens:nth-of-type(4){inset:8.3px 5.8px 8.3px 5.5px;-webkit-backdrop-filter:blur(.21px);backdrop-filter:blur(.21px)}\n\n/* the capsule and the icons; a dark copy of the icons, cut to the capsule's shape, sits on top */\n.lgn-content,.lgn-hits{inset:0}\n.lgn-content{pointer-events:none}\n.lgn-pill{position:absolute;left:111px;top:3px;width:70px;height:54px;border-radius:27px;background:#fff}\n.lgn-icons{position:absolute;inset:0}\n.lgn-icons .ic{position:absolute;top:18px;width:24px;height:24px;color:#fff}\n.lgn-icons.lgn-dark .ic{color:#33363F}\n.ic[data-i=\"0\"]{left:20px;--lv:lgn-home}\n.ic[data-i=\"1\"]{left:67px;--lv:lgn-search}\n.ic[data-i=\"2\"]{left:134px;--lv:none}\n.ic[data-i=\"3\"]{left:196px;top:17px;width:29px;height:29px;--lv:lgn-send;--lvd:760ms}\n.ic[data-i=\"4\"]{left:248px;--lv:lgn-gear;--lvd:820ms;--lve:cubic-bezier(.3,1.45,.45,1)}\n.ic-a{display:block;width:100%;height:100%;transform-origin:50% 50%;transition:transform .2s cubic-bezier(.2,.8,.2,1)}\n.ic svg{display:block}\n.ic.lgn-press .ic-a{transform:scale(.86)}\n\n/* inner shadows, drawn over everything: a wide linear burn, a grey overlay ring, and light edges that add.\n Linear burn exists only in Safari (plus-darker); elsewhere a colour burn a little stronger gives the same dark glass.\n The last two shadows of the rim are Figma's Glass light (-45°, 80%): strong on the top left, weaker on the bottom right. */\n.lgn-shade{box-shadow:inset 0 0 46.52px rgba(242,242,242,.8);mix-blend-mode:color-burn}\n@supports (mix-blend-mode:plus-darker){\n .lgn-burn{mix-blend-mode:plus-darker}\n .lgn-shade{box-shadow:inset 0 0 46.52px rgba(242,242,242,.5);mix-blend-mode:plus-darker}\n}\n.lgn-ring{box-shadow:inset 0 0 0 2.11px #999;mix-blend-mode:overlay}\n.lgn-rim{box-shadow:inset 4.23px 4.23px 2.11px -4.23px #B3B3B3,inset -4.23px -4.23px 2.11px -4.23px #B3B3B3,\n inset 12px 12px 1.5px -14px rgba(255,255,255,.75),inset -12px -12px 1.5px -14px rgba(255,255,255,.35);mix-blend-mode:plus-lighter}\n/* with the capsule at an end, the rim light steps back there, so the dark gap around the capsule reads even */\n.lgn-rim.lgn-fade{-webkit-mask-image:linear-gradient(90deg,rgba(0,0,0,calc(1 - var(--fl,0))) 0,#000 76px,#000 calc(100% - 76px),rgba(0,0,0,calc(1 - var(--fr,0))) 100%);\n mask-image:linear-gradient(90deg,rgba(0,0,0,calc(1 - var(--fl,0))) 0,#000 76px,#000 calc(100% - 76px),rgba(0,0,0,calc(1 - var(--fr,0))) 100%)}\n/* strokes, inside, 0.43px: white at 10%, and a white-to-clear gradient that adds */\n.lgn-line{box-shadow:inset 0 0 0 .43px rgba(255,255,255,.1)}\n.lgn-edge{inset:0;width:100%;height:100%;mix-blend-mode:plus-lighter;pointer-events:none}\n\n/* tap targets: one button per item, laid out by the script; on the chosen item the focus ring hugs the capsule */\n.lgn-hit{all:unset;position:absolute;top:0;height:60px;cursor:pointer;-webkit-tap-highlight-color:transparent}\n.lgn-hit:focus-visible{outline:none}\n.lgn-hit:focus-visible::after{content:\"\";position:absolute;left:calc(var(--ic) - 25px);top:5px;width:50px;height:50px;border-radius:50%;box-shadow:0 0 0 2px #9DB8FF}\n.lgn-hit[aria-current]:focus-visible::after{left:calc(var(--ic) - 36px);top:2px;width:72px;height:56px;border-radius:28px}\n.lgn-dragging,.lgn-dragging .lgn-hit{cursor:grabbing}\n\n/* each icon's gesture when it is chosen: the house jumps, the lens peeks, the message takes off, the gear turns */\n.ic.lgn-gesture .ic-a{transition:none;animation:var(--lv) var(--lvd,640ms) var(--lve,cubic-bezier(.2,.8,.2,1)) both}\n@keyframes lgn-home{0%{transform:none}28%{transform:translateY(-3.5px) scale(1.08,.9)}52%{transform:translateY(0) scale(.92,1.08)}74%{transform:scale(1.03,.97)}100%{transform:none}}\n@keyframes lgn-search{0%{transform:none}34%{transform:rotate(-26deg) scale(1.16)}68%{transform:rotate(9deg) scale(.98)}100%{transform:none}}\n@keyframes lgn-send{0%{transform:none;opacity:1}36%{transform:translate(13px,-13px) scale(.6);opacity:0}37%{transform:translate(-13px,13px) scale(.6);opacity:0}100%{transform:none;opacity:1}}\n@keyframes lgn-gear{from{transform:rotate(0)}to{transform:rotate(120deg)}}\n/* the AI does not turn: its big star takes a breath and the small one twinkles, a beat later */\n.ic[data-i=\"2\"] path{transform-box:fill-box;transform-origin:50% 50%}\n.ic[data-i=\"2\"].lgn-gesture path:first-child{animation:lgn-ai-big 560ms cubic-bezier(.2,.8,.2,1) both}\n.ic[data-i=\"2\"].lgn-gesture path:last-child{animation:lgn-ai-small 640ms cubic-bezier(.2,.8,.2,1) 120ms both}\n@keyframes lgn-ai-big{0%{transform:none}24%{transform:scale(.84)}60%{transform:scale(1.1)}100%{transform:none}}\n@keyframes lgn-ai-small{0%{transform:none}20%{transform:scale(.15)}62%{transform:scale(1.45)}100%{transform:none}}\n\n/* sparks blend by difference: dark over the white capsule, white over the glass and the page */\n.lgn-spark{position:absolute;left:0;top:0;pointer-events:none;color:#fff;mix-blend-mode:difference}\n.lgn-dot{width:4px;height:4px;margin:-2px 0 0 -2px;border-radius:50%;background:#fff}\n.lgn-star{width:var(--sz,10px);height:var(--sz,10px);margin:calc(var(--sz,10px) / -2) 0 0 calc(var(--sz,10px) / -2);filter:drop-shadow(0 0 2.5px rgba(255,255,255,.9))}\n.lgn-star svg{display:block;width:100%;height:100%}\n\n@media (prefers-reduced-motion:reduce){ .ic-a{transition:none} .ic.lgn-gesture .ic-a,.ic.lgn-gesture path{animation:none} }";
4
+ const CSS = "/* Liquid glass nav bar · Sara Lobo Library\n Figma \"Liquid Glass Button - Dark\", 291×60, for a dark app. Paint order follows the Figma layer: background blur →\n fills → lenses → capsule and icons → inner shadows → strokes. The bar stays out of any stacking context (no transform,\n opacity or isolation on it), so every blend mode mixes with what is behind it: keep it on a solid background. */\n.lgn-wrap{position:relative;display:inline-block}\n.lgn{position:relative;display:block;width:291px;height:60px;border-radius:999px;overflow:hidden;touch-action:pan-y;-webkit-user-select:none;user-select:none}\n.lgn > *{position:absolute}\n.lgn-fx{inset:0;border-radius:inherit;pointer-events:none}\n\n/* fills, bottom to top: a soft light from the top left, a grey veil and a near-white linear burn */\n.lgn-blur{-webkit-backdrop-filter:blur(15.96px);backdrop-filter:blur(15.96px)}\n.lgn-sheen{mix-blend-mode:plus-lighter;opacity:.1;background:url(\"data:image/svg+xml;utf8,<svg viewBox='0 0 291 60' xmlns='http://www.w3.org/2000/svg' preserveAspectRatio='none'><defs><radialGradient id='g' gradientUnits='userSpaceOnUse' cx='0' cy='0' r='10' gradientTransform='matrix(16.645 3.2563 -15.793 3.4319 38.852 9.8387)'><stop stop-color='%23fff'/><stop offset='1' stop-color='%23fff' stop-opacity='0'/></radialGradient></defs><rect width='291' height='60' fill='url(%23g)'/></svg>\") 0 0/100% 100%}\n.lgn-veil{background:rgba(51,51,51,.2)}\n.lgn-burn{background:#E6E6E6;mix-blend-mode:color-burn}\n\n/* lenses: four nested capsules, each a little smaller, each blurring what is behind it a little less */\n.lgn-lens{border-radius:999px;background:rgba(255,255,255,.01);pointer-events:none}\n.lgn-lens:nth-of-type(1){inset:0 .09px 0 -.21px;-webkit-backdrop-filter:blur(10.64px);backdrop-filter:blur(10.64px)}\n.lgn-lens:nth-of-type(2){inset:.64px .52px .64px .23px;-webkit-backdrop-filter:blur(5.32px);backdrop-filter:blur(5.32px)}\n.lgn-lens:nth-of-type(3){inset:1.91px 1.4px 1.91px 1.11px;-webkit-backdrop-filter:blur(2.66px);backdrop-filter:blur(2.66px)}\n.lgn-lens:nth-of-type(4){inset:8.3px 5.8px 8.3px 5.5px;-webkit-backdrop-filter:blur(.21px);backdrop-filter:blur(.21px)}\n\n/* the capsule and the icons; a dark copy of the icons, cut to the capsule's shape, sits on top */\n.lgn-content,.lgn-hits{inset:0}\n.lgn-content{pointer-events:none}\n.lgn-pill{position:absolute;left:111px;top:3px;width:70px;height:54px;border-radius:27px;background:#fff}\n.lgn-icons{position:absolute;inset:0}\n.lgn-icons .ic{position:absolute;top:18px;width:24px;height:24px;color:#fff}\n.lgn-icons.lgn-dark .ic{color:#33363F}\n.ic[data-i=\"0\"]{left:20px;--lv:lgn-home}\n.ic[data-i=\"1\"]{left:67px;--lv:lgn-search}\n.ic[data-i=\"2\"]{left:134px;--lv:none}\n.ic[data-i=\"3\"]{left:196px;top:17px;width:29px;height:29px;--lv:lgn-send;--lvd:760ms}\n.ic[data-i=\"4\"]{left:248px;--lv:lgn-gear;--lvd:820ms;--lve:cubic-bezier(.3,1.45,.45,1)}\n.ic-a{display:block;width:100%;height:100%;transform-origin:50% 50%;transition:transform .2s cubic-bezier(.2,.8,.2,1)}\n.ic svg{display:block}\n.ic.lgn-press .ic-a{transform:scale(.86)}\n\n/* inner shadows, drawn over everything: a wide linear burn, a grey overlay ring, and light edges that add.\n Linear burn exists only in Safari (plus-darker); elsewhere a colour burn a little stronger gives the same dark glass.\n The last two shadows of the rim are Figma's Glass light (-45°, 80%): strong on the top left, weaker on the bottom right. */\n.lgn-shade{box-shadow:inset 0 0 46.52px rgba(242,242,242,.8);mix-blend-mode:color-burn}\n@supports (mix-blend-mode:plus-darker){\n .lgn-burn{mix-blend-mode:plus-darker}\n .lgn-shade{box-shadow:inset 0 0 46.52px rgba(242,242,242,.5);mix-blend-mode:plus-darker}\n}\n.lgn-ring{box-shadow:inset 0 0 0 2.11px #999;mix-blend-mode:overlay}\n.lgn-rim{box-shadow:inset 4.23px 4.23px 2.11px -4.23px #B3B3B3,inset -4.23px -4.23px 2.11px -4.23px #B3B3B3,\n inset 12px 12px 1.5px -14px rgba(255,255,255,.75),inset -12px -12px 1.5px -14px rgba(255,255,255,.35);mix-blend-mode:plus-lighter}\n/* with the capsule at an end, the rim light steps back there, so the dark gap around the capsule reads even */\n.lgn-rim.lgn-fade{-webkit-mask-image:linear-gradient(90deg,rgba(0,0,0,calc(1 - var(--fl,0))) 0,#000 76px,#000 calc(100% - 76px),rgba(0,0,0,calc(1 - var(--fr,0))) 100%);\n mask-image:linear-gradient(90deg,rgba(0,0,0,calc(1 - var(--fl,0))) 0,#000 76px,#000 calc(100% - 76px),rgba(0,0,0,calc(1 - var(--fr,0))) 100%)}\n/* strokes, inside, 0.43px: white at 10%, and a white-to-clear gradient that adds */\n.lgn-line{box-shadow:inset 0 0 0 .43px rgba(255,255,255,.1)}\n.lgn-edge{inset:0;width:100%;height:100%;mix-blend-mode:plus-lighter;pointer-events:none}\n\n/* tap targets: one button per item, laid out by the script; on the chosen item the focus ring hugs the capsule */\n.lgn-hit{all:unset;position:absolute;top:0;height:60px;cursor:pointer;-webkit-tap-highlight-color:transparent}\n.lgn-hit:focus-visible{outline:none}\n.lgn-hit:focus-visible::after{content:\"\";position:absolute;left:calc(var(--ic) - 25px);top:5px;width:50px;height:50px;border-radius:50%;box-shadow:0 0 0 2px #9DB8FF}\n.lgn-hit[aria-current]:focus-visible::after{left:calc(var(--ic) - 36px);top:2px;width:72px;height:56px;border-radius:28px}\n.lgn-dragging,.lgn-dragging .lgn-hit{cursor:grabbing}\n\n/* each icon's gesture when it is chosen: the house jumps, the lens peeks, the message takes off, the gear turns */\n.ic.lgn-gesture .ic-a{transition:none;animation:var(--lv) var(--lvd,640ms) var(--lve,cubic-bezier(.2,.8,.2,1)) both}\n@keyframes lgn-home{0%{transform:none}28%{transform:translateY(-3.5px) scale(1.08,.9)}52%{transform:translateY(0) scale(.92,1.08)}74%{transform:scale(1.03,.97)}100%{transform:none}}\n@keyframes lgn-search{0%{transform:none}34%{transform:rotate(-26deg) scale(1.16)}68%{transform:rotate(9deg) scale(.98)}100%{transform:none}}\n@keyframes lgn-send{0%{transform:none;opacity:1}36%{transform:translate(13px,-13px) scale(.6);opacity:0}37%{transform:translate(-13px,13px) scale(.6);opacity:0}100%{transform:none;opacity:1}}\n@keyframes lgn-gear{from{transform:rotate(0)}to{transform:rotate(120deg)}}\n/* the AI does not turn: its big star takes a breath and the small one twinkles, a beat later */\n.ic[data-i=\"2\"] path{transform-box:fill-box;transform-origin:50% 50%}\n.ic[data-i=\"2\"].lgn-gesture path:first-child{animation:lgn-ai-big 560ms cubic-bezier(.2,.8,.2,1) both}\n.ic[data-i=\"2\"].lgn-gesture path:last-child{animation:lgn-ai-small 640ms cubic-bezier(.2,.8,.2,1) 120ms both}\n@keyframes lgn-ai-big{0%{transform:none}24%{transform:scale(.84)}60%{transform:scale(1.1)}100%{transform:none}}\n@keyframes lgn-ai-small{0%{transform:none}20%{transform:scale(.15)}62%{transform:scale(1.45)}100%{transform:none}}\n\n/* sparks blend by difference: dark over the white capsule, white over the glass and the page */\n.lgn-spark{position:absolute;left:0;top:0;pointer-events:none;color:#fff;mix-blend-mode:difference}\n.lgn-dot{width:4px;height:4px;margin:-2px 0 0 -2px;border-radius:50%;background:#fff}\n.lgn-star{width:var(--sz,10px);height:var(--sz,10px);margin:calc(var(--sz,10px) / -2) 0 0 calc(var(--sz,10px) / -2);filter:drop-shadow(0 0 2.5px rgba(255,255,255,.9))}\n.lgn-star svg{display:block;width:100%;height:100%}\n\n@media (prefers-reduced-motion:reduce){ .ic-a{transition:none} .ic.lgn-gesture .ic-a,.ic.lgn-gesture path{animation:none} }";
5
5
  const NAV = "<nav class=\"lgn\" aria-label=\"Navegação principal\">\n <span class=\"lgn-fx lgn-blur\" aria-hidden=\"true\"></span>\n <span class=\"lgn-fx lgn-sheen\" aria-hidden=\"true\"></span>\n <span class=\"lgn-fx lgn-veil\" aria-hidden=\"true\"></span>\n <span class=\"lgn-fx lgn-burn\" aria-hidden=\"true\"></span>\n <i class=\"lgn-lens\" aria-hidden=\"true\"></i>\n <i class=\"lgn-lens\" aria-hidden=\"true\"></i>\n <i class=\"lgn-lens\" aria-hidden=\"true\"></i>\n <i class=\"lgn-lens\" aria-hidden=\"true\"></i>\n <div class=\"lgn-content\" aria-hidden=\"true\">\n <span class=\"lgn-pill\"></span>\n <div class=\"lgn-icons\">\n <span class=\"ic\" data-i=\"0\"><span class=\"ic-a\"><svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M8.09525 7.34693L7.09525 8.20407C6.06437 9.08769 5.54892 9.5295 5.27446 10.1262C5 10.723 5 11.4019 5 12.7596V17C5 18.8856 5 19.8284 5.58579 20.4142C6.17157 21 7.11438 21 9 21H15C16.8856 21 17.8284 21 18.4142 20.4142C19 19.8284 19 18.8856 19 17V12.7596C19 11.4019 19 10.723 18.7255 10.1262C18.4511 9.5295 17.9356 9.08769 16.9047 8.20407L15.9047 7.34693C14.0414 5.7498 13.1098 4.95123 12 4.95123C10.8902 4.95123 9.95857 5.7498 8.09525 7.34693Z\" stroke-width=\"2\" stroke=\"currentColor\"/><path d=\"M14.5 21V16C14.5 15.4477 14.0523 15 13.5 15H10.5C9.94772 15 9.5 15.4477 9.5 16V21\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke=\"currentColor\"/></svg></span></span>\n <span class=\"ic\" data-i=\"1\"><span class=\"ic-a\"><svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\"><circle cx=\"11\" cy=\"11\" r=\"7\" stroke=\"currentColor\" stroke-width=\"2\"/><path d=\"M20 20L17 17\" stroke-width=\"2\" stroke-linecap=\"round\" stroke=\"currentColor\"/></svg></span></span>\n <span class=\"ic\" data-i=\"2\"><span class=\"ic-a\"><svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M10 5C11.1 10.5 12.5 11.9 18 13C12.5 14.1 11.1 15.5 10 21C8.9 15.5 7.5 14.1 2 13C7.5 11.9 8.9 10.5 10 5Z\" stroke-width=\"2\" stroke-linejoin=\"round\" stroke=\"currentColor\"/><path d=\"M18.5 2C19.025 4.50833 19.4917 4.975 22 5.5C19.4917 6.025 19.025 6.49167 18.5 9C17.975 6.49167 17.5083 6.025 15 5.5C17.5083 4.975 17.975 4.50833 18.5 2Z\" stroke-width=\"2\" stroke-linejoin=\"round\" stroke=\"currentColor\"/></svg></span></span>\n <span class=\"ic\" data-i=\"3\"><span class=\"ic-a\"><svg width=\"29\" height=\"29\" viewBox=\"0 0 29 29\" fill=\"none\"><path d=\"M21.7229 4.69296C22.3309 4.58484 23.0897 4.56034 23.6975 5.07284L23.8176 5.18222L23.927 5.30234C24.4396 5.9102 24.415 6.66895 24.3069 7.27695C24.1902 7.93275 23.9088 8.76704 23.5862 9.73495L20.9719 17.5787C20.6466 18.5546 20.3903 19.3235 20.1673 19.8951C19.9575 20.4327 19.7221 20.9533 19.3801 21.3092C18.0357 22.7078 15.7967 22.7081 14.4524 21.3092C14.1104 20.9533 13.8751 20.4328 13.6653 19.8951C13.4423 19.3235 13.186 18.5547 12.8606 17.5787C12.7174 17.149 12.6787 17.0428 12.6331 16.9566C12.5001 16.7053 12.2946 16.4997 12.0432 16.3668C11.957 16.3212 11.8506 16.2824 11.4212 16.1393C10.4452 15.8139 9.67634 15.5576 9.10476 15.3346C8.56711 15.1248 8.04658 14.8894 7.69069 14.5475C6.29189 13.2031 6.29205 10.9642 7.69069 9.61972C8.04654 9.27777 8.56717 9.04238 9.10476 8.83261C9.67631 8.60961 10.4453 8.35321 11.4212 8.02792L19.2649 5.41366C20.2328 5.09104 21.0671 4.80968 21.7229 4.69296ZM22.3694 6.62949C22.3077 6.63039 22.2122 6.63784 22.0725 6.66269C21.5961 6.74758 20.9287 6.96713 19.8967 7.31113L12.054 9.92538C11.0567 10.2578 10.3447 10.4956 9.83132 10.6959C9.57729 10.795 9.3942 10.8767 9.26296 10.9459C9.19881 10.9798 9.15281 11.0076 9.12136 11.0289C9.10613 11.0392 9.09448 11.0475 9.08718 11.0533C9.08382 11.056 9.08102 11.0587 9.07937 11.0601C9.07792 11.0613 9.07644 11.0621 9.07644 11.0621C8.49698 11.6195 8.49681 12.5478 9.07644 13.1051C9.07644 13.1051 9.0778 13.1057 9.07937 13.107C9.08106 13.1085 9.0837 13.1111 9.08718 13.1139C9.09452 13.1197 9.10601 13.1279 9.12136 13.1383C9.15284 13.1596 9.1987 13.1874 9.26296 13.2213C9.39423 13.2905 9.5772 13.3721 9.83132 13.4713C10.3447 13.6716 11.0566 13.9093 12.054 14.2418C12.4194 14.3636 12.7114 14.4584 12.9778 14.5992C13.5842 14.9199 14.0799 15.4156 14.4007 16.0221C14.5416 16.2885 14.6362 16.5804 14.7581 16.9459C15.0906 17.9434 15.3283 18.6551 15.5286 19.1685C15.6278 19.4228 15.7093 19.6056 15.7786 19.7369C15.8125 19.8012 15.8402 19.847 15.8616 19.8785C15.872 19.8939 15.8801 19.9053 15.886 19.9127C15.8888 19.9162 15.8913 19.9188 15.8928 19.9205C15.8943 19.9221 15.8948 19.9234 15.8948 19.9234C16.4521 20.5031 17.3803 20.5029 17.9378 19.9234C17.9378 19.9234 17.9385 19.922 17.9397 19.9205C17.9412 19.9188 17.9438 19.9161 17.9466 19.9127C17.9524 19.9054 17.9606 19.8937 17.971 19.8785C17.9923 19.8471 18.0201 19.8011 18.054 19.7369C18.1232 19.6057 18.2048 19.4226 18.304 19.1685C18.5043 18.6552 18.742 17.9432 19.0745 16.9459L21.6887 9.10312C22.0328 8.07105 22.2523 7.40381 22.3372 6.92734C22.3622 6.7868 22.3686 6.69111 22.3694 6.62949Z\" fill=\"currentColor\"/></svg></span></span>\n <span class=\"ic\" data-i=\"4\"><span class=\"ic-a\"><svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M4.43094 7.63L3.08169 10.0555C2.55298 11.0059 2.28862 11.4812 2.28862 12C2.28862 12.5188 2.55298 12.9941 3.08169 13.9445L4.43094 16.37L5.85685 18.7512C6.41561 19.6843 6.69498 20.1509 7.14431 20.4103C7.59364 20.6697 8.13737 20.6784 9.22483 20.6957L12 20.74L14.7752 20.6957C15.8626 20.6784 16.4064 20.6697 16.8557 20.4103C17.305 20.1509 17.5844 19.6843 18.1431 18.7512L19.5691 16.37L20.9183 13.9445C21.447 12.9941 21.7114 12.5188 21.7114 12C21.7114 11.4812 21.447 11.0059 20.9183 10.0555L19.5691 7.63L18.1431 5.24876C17.5844 4.31567 17.305 3.84912 16.8557 3.5897C16.4064 3.33028 15.8626 3.3216 14.7752 3.30426L12 3.26L9.22483 3.30426C8.13737 3.3216 7.59364 3.33028 7.14431 3.5897C6.69498 3.84912 6.4156 4.31567 5.85685 5.24876L4.43094 7.63Z\" stroke-width=\"2\" stroke=\"currentColor\"/><circle cx=\"12\" cy=\"12\" r=\"3\" stroke=\"currentColor\" stroke-width=\"2\"/></svg></span></span>\n </div>\n </div>\n <span class=\"lgn-fx lgn-shade\" aria-hidden=\"true\"></span>\n <span class=\"lgn-fx lgn-ring\" aria-hidden=\"true\"></span>\n <span class=\"lgn-fx lgn-rim\" aria-hidden=\"true\"></span>\n <span class=\"lgn-fx lgn-line\" aria-hidden=\"true\"></span>\n <svg class=\"lgn-edge\" viewBox=\"0 0 291 60\" preserveAspectRatio=\"none\" aria-hidden=\"true\"><defs><linearGradient id=\"lgn-edge\" gradientUnits=\"userSpaceOnUse\" x1=\"0\" y1=\".5\" x2=\"1\" y2=\".5\" gradientTransform=\"matrix(257.59 54.558 -264.61 53.112 165.7 -17.154)\"><stop stop-color=\"#fff\"/><stop offset=\"1\" stop-color=\"#fff\" stop-opacity=\"0\"/></linearGradient></defs><rect x=\".2128\" y=\".2128\" width=\"290.5745\" height=\"59.5745\" rx=\"29.787\" fill=\"none\" stroke=\"url(#lgn-edge)\" stroke-opacity=\".1\" stroke-width=\".4255\"/></svg>\n <div class=\"lgn-hits\">\n <button class=\"lgn-hit\" type=\"button\" aria-label=\"Início\"></button>\n <button class=\"lgn-hit\" type=\"button\" aria-label=\"Buscar\"></button>\n <button class=\"lgn-hit\" type=\"button\" aria-label=\"Perguntar à IA\"></button>\n <button class=\"lgn-hit\" type=\"button\" aria-label=\"Mensagens\"></button>\n <button class=\"lgn-hit\" type=\"button\" aria-label=\"Ajustes\"></button>\n </div>\n</nav>";
6
6
  let api;
7
7
  function engine() {
package/dist/index.js CHANGED
@@ -7,6 +7,7 @@ import { mountGlassNav } from './glassnav.js';
7
7
  import { mountIridescentCard } from './card-mount.js';
8
8
  import { mountPiggyBank } from './piggy-mount.js';
9
9
  import { mountThinking } from './thinking.js';
10
+ import { mountPanoramaReel } from './panorama-mount.js';
10
11
 
11
12
  const HOST = { display: 'inline-block', lineHeight: 0, verticalAlign: 'middle' };
12
13
 
@@ -87,4 +88,18 @@ function thinkingComponent(kind, name) {
87
88
  export const MagicSphere = thinkingComponent('sphere', 'MagicSphere');
88
89
  export const SweepSphere = thinkingComponent('sweep', 'SweepSphere');
89
90
 
90
- export { mountLike, mountMagnet, mountGlassNav, mountIridescentCard, mountPiggyBank, mountThinking };
91
+ // The Panorama fills the box it renders (100% wide and `height` tall, 640px by default): a hero for moving pictures.
92
+ // With no `video` and no `stills` it shows the library's own clip and pictures; pass yours to make it your own (the
93
+ // pictures must come from your site or one that allows CORS). replay: change it (a counter) to play the opening again.
94
+ export function PanoramaReel({ height = 640, title, text, textItalic, video, stills, labels, intro, cursor, fonts, replay, className, style }) {
95
+ const host = useRef(null), dome = useRef(null), last = useRef(replay);
96
+ const key = JSON.stringify([title, text, textItalic, video, stills, labels, intro, cursor, fonts]);
97
+ useEffect(() => {
98
+ const m = dome.current = mountPanoramaReel(host.current, { title, text, textItalic, video, stills, labels, intro, cursor, fonts });
99
+ return () => { m.destroy(); dome.current = null; };
100
+ }, [key]);
101
+ useEffect(() => { if (last.current === replay) return; last.current = replay; if (dome.current) dome.current.replay(); }, [replay]);
102
+ return createElement('div', { ref: host, className, style: { position: 'relative', width: '100%', height, ...style } });
103
+ }
104
+
105
+ export { mountLike, mountMagnet, mountGlassNav, mountIridescentCard, mountPiggyBank, mountThinking, mountPanoramaReel };
package/dist/like.js CHANGED
@@ -1,12 +1,12 @@
1
1
  // Bursting heart · Craft Library, by Sara Lobo. like.css and like.js from the library, unchanged, around a mount.
2
2
  import { css } from './style.js';
3
- const CSS = "/* Bursting heart · Craft Library, by Sara Lobo\n The heart eases out of sight, a disc grows and hollows into a ring, 14 dots and 2 or 3 small hearts fly out,\n and the heart springs back full. Every motion runs on its own property with its own curve; where two curves\n meet, both are at rest there, so nothing hitches. Unliking only takes the colour away. Pairs with like.js. */\n\n:root {\n --like-heart: #FF6767; /* the liked heart, filled (Figma: General / Heart) */\n --like-color: #F91880; /* the disc */\n --like-ring: #CC8EF5; /* the ring as it opens */\n --like-spring: cubic-bezier(.34, 1.4, .64, 1); /* fallback for browsers without linear() */\n}\n\n/* a damped spring (damping .55, 700 ms): about 12% over, a short settle, at rest at the end */\n@supports (animation-timing-function: linear(0, 1)) {\n :root {\n --like-spring: linear(0, 0.034, 0.12, 0.24, 0.378, 0.52, 0.655, 0.778, 0.883, 0.968, 1.033, 1.079, 1.108, 1.123, 1.126, 1.12, 1.108, 1.092, 1.075, 1.057, 1.04, 1.025, 1.012, 1.002, 0.994, 0.989, 0.986, 0.984, 0.984, 0.985, 0.987, 0.989, 0.991, 0.993, 0.995, 0.997, 0.999, 1, 1.001, 1.001, 1);\n }\n}\n\n/* lets the ring's hole open smoothly (without it, the ring leaves at once) */\n@property --like-hole {\n syntax: '<percentage>';\n inherits: false;\n initial-value: 0%;\n}\n\n/* the button: a 40px circle in black at 60%, made to sit over images */\n.like {\n all: unset;\n box-sizing: border-box;\n position: relative;\n display: inline-block;\n flex: none;\n width: 40px;\n height: 40px;\n border-radius: 50%;\n background: rgba(0, 0, 0, .6);\n cursor: pointer;\n -webkit-tap-highlight-color: transparent;\n transition: background .2s ease, transform .22s cubic-bezier(.2, .8, .2, 1);\n}\n.like:hover {\n background: rgba(0, 0, 0, .72);\n}\n.like:active {\n transform: scale(.94);\n}\n.like:focus-visible {\n outline: 2px solid #fff;\n outline-offset: 2px;\n}\n.like-icon {\n position: absolute;\n left: 8px;\n top: 9px;\n width: 24px;\n height: 24px;\n}\n.like-icon svg {\n display: block;\n overflow: visible;\n}\n.like-line {\n fill: none;\n stroke: #fff;\n stroke-width: 2;\n stroke-linecap: round;\n stroke-linejoin: round;\n transition: stroke .24s cubic-bezier(.4, 0, .2, 1);\n}\n.like-fill {\n fill: var(--like-heart);\n opacity: 0;\n transition: opacity .24s cubic-bezier(.4, 0, .2, 1);\n}\n.like[aria-pressed=\"true\"] .like-line {\n stroke: var(--like-heart);\n}\n.like[aria-pressed=\"true\"] .like-fill {\n opacity: 1;\n}\n/* the colour swaps while the heart is out of sight */\n.like.is-liking .like-line,\n.like.is-liking .like-fill {\n transition-duration: 0s;\n transition-delay: .3s;\n}\n/* everything that flies out starts from the heart's centre */\n.like-fx {\n position: absolute;\n left: 20px;\n top: 21px;\n width: 0;\n height: 0;\n pointer-events: none;\n}\n\n/* the heart: out of sight in 120 ms, waits inside the disc, then the spring brings it back full */\n.like.is-liking .like-icon {\n animation: like-vanish .12s cubic-bezier(.5, 0, .75, 0) both, like-return .7s var(--like-spring) .32s forwards;\n}\n@keyframes like-vanish {\n from {\n scale: 1;\n opacity: 1;\n }\n to {\n scale: .15;\n opacity: 0;\n }\n}\n@keyframes like-return {\n from {\n scale: .15;\n opacity: 0;\n }\n to {\n scale: 1;\n opacity: 1;\n }\n}\n\n/* unliking: a soft dip and back while the colour leaves, at rest at both ends */\n.like.is-unliking .like-icon {\n animation: like-dip .34s cubic-bezier(.37, 0, .63, 1);\n}\n@keyframes like-dip {\n 0% {\n scale: 1;\n }\n 40% {\n scale: .86;\n }\n 100% {\n scale: 1;\n }\n}\n\n/* the ring: one long ease-out growth (to 110%), the tint, and a hole that opens before the growth ends */\n.like-ring {\n position: absolute;\n left: -23px;\n top: -23px;\n width: 46px;\n height: 46px;\n border-radius: 50%;\n background: var(--like-color);\n scale: 0;\n -webkit-mask: radial-gradient(circle closest-side, transparent var(--like-hole), #000 calc(var(--like-hole) + 2%));\n mask: radial-gradient(circle closest-side, transparent var(--like-hole), #000 calc(var(--like-hole) + 2%));\n}\n.like.is-liking .like-ring {\n animation: like-ring-grow .56s cubic-bezier(.22, 1, .36, 1) .06s both, like-ring-tint .3s ease-out .06s both, like-ring-open .36s cubic-bezier(.33, 1, .68, 1) .28s both;\n}\n@keyframes like-ring-grow {\n from {\n scale: 0;\n }\n to {\n scale: 1.1;\n }\n}\n@keyframes like-ring-tint {\n from {\n background-color: var(--like-color);\n }\n to {\n background-color: var(--like-ring);\n }\n}\n@keyframes like-ring-open {\n from {\n --like-hole: 0%;\n }\n to {\n --like-hole: 100%;\n }\n}\n\n/* the dots: 7 pairs around the ring. They fly out on a long ease-out, swell and shrink on their own clock,\n and the whole spray turns 12° as it goes */\n.like-dots i {\n position: absolute;\n left: 0;\n top: 0;\n width: 0;\n height: 0;\n transform: rotate(var(--a));\n}\n.like-dots b {\n position: absolute;\n left: -2.5px;\n top: -2.5px;\n width: 5px;\n height: 5px;\n border-radius: 50%;\n background: var(--d1);\n scale: 0;\n}\n.like-dots b + b {\n left: -1.75px;\n top: -1.75px;\n width: 3.5px;\n height: 3.5px;\n background: var(--d2);\n}\n.like-dots i:nth-child(1) { --a: 0deg; --d1: #F91880; --d2: #FF7AB6; }\n.like-dots i:nth-child(2) { --a: 51.43deg; --d1: #FFAD1F; --d2: #FFD166; }\n.like-dots i:nth-child(3) { --a: 102.86deg; --d1: #1D9BF0; --d2: #7CC6FE; }\n.like-dots i:nth-child(4) { --a: 154.29deg; --d1: #A78BFA; --d2: #C4B5FD; }\n.like-dots i:nth-child(5) { --a: 205.71deg; --d1: #00BA7C; --d2: #6EE7B7; }\n.like-dots i:nth-child(6) { --a: 257.14deg; --d1: #F97316; --d2: #FDBA74; }\n.like-dots i:nth-child(7) { --a: 308.57deg; --d1: #F91880; --d2: #CC8EF5; }\n.like.is-liking .like-dots i {\n animation: like-swirl .9s cubic-bezier(.22, 1, .36, 1) .34s both;\n}\n.like.is-liking .like-dots b {\n animation: like-fly .62s cubic-bezier(.16, 1, .3, 1) .34s both, like-dot .62s .34s both;\n}\n.like.is-liking .like-dots b + b {\n animation-name: like-fly-2, like-dot;\n animation-delay: .38s;\n}\n@keyframes like-swirl {\n from {\n rotate: 0deg;\n }\n to {\n rotate: 12deg;\n }\n}\n@keyframes like-fly {\n from {\n translate: 0 -20px;\n }\n to {\n translate: 0 -33px;\n }\n}\n@keyframes like-fly-2 {\n from {\n translate: 4.5px -18px;\n }\n to {\n translate: 8px -29px;\n }\n}\n@keyframes like-dot {\n 0% {\n scale: 0;\n animation-timing-function: cubic-bezier(.33, 1, .68, 1);\n }\n 24% {\n scale: 1;\n animation-timing-function: cubic-bezier(.32, 0, .67, 0);\n }\n 100% {\n scale: 0;\n }\n}\n\n/* the small hearts (like.js adds them): the rise is one unbroken ease-out, the sway a sine that rests at its turns,\n and size and fade keep their own clocks */\n.like-float {\n position: absolute;\n left: 0;\n top: -6px;\n opacity: 0;\n animation: like-rise var(--dur) cubic-bezier(.33, 1, .68, 1) var(--dl) both, like-fade var(--dur) var(--dl) both;\n}\n.like-float span {\n display: block;\n animation: like-sway var(--dur) cubic-bezier(.37, 0, .63, 1) var(--dl) both, like-grow var(--dur) var(--dl) both;\n}\n.like-float svg {\n display: block;\n width: var(--s);\n height: var(--s);\n margin: calc(var(--s) / -2) 0 0 calc(var(--s) / -2);\n fill: var(--fc);\n}\n@keyframes like-rise {\n from {\n translate: 0 0;\n }\n to {\n translate: 0 var(--dy);\n }\n}\n@keyframes like-fade {\n 0% {\n opacity: 0;\n animation-timing-function: cubic-bezier(.33, 1, .68, 1);\n }\n 14% {\n opacity: 1;\n animation-timing-function: linear;\n }\n 50% {\n opacity: 1;\n animation-timing-function: cubic-bezier(.37, 0, .63, 1);\n }\n 100% {\n opacity: 0;\n }\n}\n@keyframes like-sway {\n 0% {\n translate: 0 0;\n rotate: 0deg;\n }\n 33% {\n translate: var(--dx) 0;\n rotate: var(--r);\n }\n 70% {\n translate: calc(var(--dx) * -.6) 0;\n rotate: calc(var(--r) * -.6);\n }\n 100% {\n translate: calc(var(--dx) * .25) 0;\n rotate: 0deg;\n }\n}\n@keyframes like-grow {\n 0% {\n scale: .3;\n animation-timing-function: cubic-bezier(.34, 1.56, .64, 1);\n }\n 24% {\n scale: 1;\n animation-timing-function: cubic-bezier(.32, 0, .67, 0);\n }\n 100% {\n scale: .8;\n }\n}\n\n/* less motion: only the colour changes */\n@media (prefers-reduced-motion: reduce) {\n .like,\n .like * {\n animation: none !important;\n }\n}\n";
3
+ const CSS = "/* Bursting heart · Sara Lobo Library\n The heart eases out of sight, a disc grows and hollows into a ring, 14 dots and 2 or 3 small hearts fly out,\n and the heart springs back full. Every motion runs on its own property with its own curve; where two curves\n meet, both are at rest there, so nothing hitches. Unliking only takes the colour away. Pairs with like.js. */\n\n:root {\n --like-heart: #FF6767; /* the liked heart, filled (Figma: General / Heart) */\n --like-color: #F91880; /* the disc */\n --like-ring: #CC8EF5; /* the ring as it opens */\n --like-spring: cubic-bezier(.34, 1.4, .64, 1); /* fallback for browsers without linear() */\n}\n\n/* a damped spring (damping .55, 700 ms): about 12% over, a short settle, at rest at the end */\n@supports (animation-timing-function: linear(0, 1)) {\n :root {\n --like-spring: linear(0, 0.034, 0.12, 0.24, 0.378, 0.52, 0.655, 0.778, 0.883, 0.968, 1.033, 1.079, 1.108, 1.123, 1.126, 1.12, 1.108, 1.092, 1.075, 1.057, 1.04, 1.025, 1.012, 1.002, 0.994, 0.989, 0.986, 0.984, 0.984, 0.985, 0.987, 0.989, 0.991, 0.993, 0.995, 0.997, 0.999, 1, 1.001, 1.001, 1);\n }\n}\n\n/* lets the ring's hole open smoothly (without it, the ring leaves at once) */\n@property --like-hole {\n syntax: '<percentage>';\n inherits: false;\n initial-value: 0%;\n}\n\n/* the button: a 40px circle in black at 60%, made to sit over images */\n.like {\n all: unset;\n box-sizing: border-box;\n position: relative;\n display: inline-block;\n flex: none;\n width: 40px;\n height: 40px;\n border-radius: 50%;\n background: rgba(0, 0, 0, .6);\n cursor: pointer;\n -webkit-tap-highlight-color: transparent;\n transition: background .2s ease, transform .22s cubic-bezier(.2, .8, .2, 1);\n}\n.like:hover {\n background: rgba(0, 0, 0, .72);\n}\n.like:active {\n transform: scale(.94);\n}\n.like:focus-visible {\n outline: 2px solid #fff;\n outline-offset: 2px;\n}\n.like-icon {\n position: absolute;\n left: 8px;\n top: 9px;\n width: 24px;\n height: 24px;\n}\n.like-icon svg {\n display: block;\n overflow: visible;\n}\n.like-line {\n fill: none;\n stroke: #fff;\n stroke-width: 2;\n stroke-linecap: round;\n stroke-linejoin: round;\n transition: stroke .24s cubic-bezier(.4, 0, .2, 1);\n}\n.like-fill {\n fill: var(--like-heart);\n opacity: 0;\n transition: opacity .24s cubic-bezier(.4, 0, .2, 1);\n}\n.like[aria-pressed=\"true\"] .like-line {\n stroke: var(--like-heart);\n}\n.like[aria-pressed=\"true\"] .like-fill {\n opacity: 1;\n}\n/* the colour swaps while the heart is out of sight */\n.like.is-liking .like-line,\n.like.is-liking .like-fill {\n transition-duration: 0s;\n transition-delay: .3s;\n}\n/* everything that flies out starts from the heart's centre */\n.like-fx {\n position: absolute;\n left: 20px;\n top: 21px;\n width: 0;\n height: 0;\n pointer-events: none;\n}\n\n/* the heart: out of sight in 120 ms, waits inside the disc, then the spring brings it back full */\n.like.is-liking .like-icon {\n animation: like-vanish .12s cubic-bezier(.5, 0, .75, 0) both, like-return .7s var(--like-spring) .32s forwards;\n}\n@keyframes like-vanish {\n from {\n scale: 1;\n opacity: 1;\n }\n to {\n scale: .15;\n opacity: 0;\n }\n}\n@keyframes like-return {\n from {\n scale: .15;\n opacity: 0;\n }\n to {\n scale: 1;\n opacity: 1;\n }\n}\n\n/* unliking: a soft dip and back while the colour leaves, at rest at both ends */\n.like.is-unliking .like-icon {\n animation: like-dip .34s cubic-bezier(.37, 0, .63, 1);\n}\n@keyframes like-dip {\n 0% {\n scale: 1;\n }\n 40% {\n scale: .86;\n }\n 100% {\n scale: 1;\n }\n}\n\n/* the ring: one long ease-out growth (to 110%), the tint, and a hole that opens before the growth ends */\n.like-ring {\n position: absolute;\n left: -23px;\n top: -23px;\n width: 46px;\n height: 46px;\n border-radius: 50%;\n background: var(--like-color);\n scale: 0;\n -webkit-mask: radial-gradient(circle closest-side, transparent var(--like-hole), #000 calc(var(--like-hole) + 2%));\n mask: radial-gradient(circle closest-side, transparent var(--like-hole), #000 calc(var(--like-hole) + 2%));\n}\n.like.is-liking .like-ring {\n animation: like-ring-grow .56s cubic-bezier(.22, 1, .36, 1) .06s both, like-ring-tint .3s ease-out .06s both, like-ring-open .36s cubic-bezier(.33, 1, .68, 1) .28s both;\n}\n@keyframes like-ring-grow {\n from {\n scale: 0;\n }\n to {\n scale: 1.1;\n }\n}\n@keyframes like-ring-tint {\n from {\n background-color: var(--like-color);\n }\n to {\n background-color: var(--like-ring);\n }\n}\n@keyframes like-ring-open {\n from {\n --like-hole: 0%;\n }\n to {\n --like-hole: 100%;\n }\n}\n\n/* the dots: 7 pairs around the ring. They fly out on a long ease-out, swell and shrink on their own clock,\n and the whole spray turns 12° as it goes */\n.like-dots i {\n position: absolute;\n left: 0;\n top: 0;\n width: 0;\n height: 0;\n transform: rotate(var(--a));\n}\n.like-dots b {\n position: absolute;\n left: -2.5px;\n top: -2.5px;\n width: 5px;\n height: 5px;\n border-radius: 50%;\n background: var(--d1);\n scale: 0;\n}\n.like-dots b + b {\n left: -1.75px;\n top: -1.75px;\n width: 3.5px;\n height: 3.5px;\n background: var(--d2);\n}\n.like-dots i:nth-child(1) { --a: 0deg; --d1: #F91880; --d2: #FF7AB6; }\n.like-dots i:nth-child(2) { --a: 51.43deg; --d1: #FFAD1F; --d2: #FFD166; }\n.like-dots i:nth-child(3) { --a: 102.86deg; --d1: #1D9BF0; --d2: #7CC6FE; }\n.like-dots i:nth-child(4) { --a: 154.29deg; --d1: #A78BFA; --d2: #C4B5FD; }\n.like-dots i:nth-child(5) { --a: 205.71deg; --d1: #00BA7C; --d2: #6EE7B7; }\n.like-dots i:nth-child(6) { --a: 257.14deg; --d1: #F97316; --d2: #FDBA74; }\n.like-dots i:nth-child(7) { --a: 308.57deg; --d1: #F91880; --d2: #CC8EF5; }\n.like.is-liking .like-dots i {\n animation: like-swirl .9s cubic-bezier(.22, 1, .36, 1) .34s both;\n}\n.like.is-liking .like-dots b {\n animation: like-fly .62s cubic-bezier(.16, 1, .3, 1) .34s both, like-dot .62s .34s both;\n}\n.like.is-liking .like-dots b + b {\n animation-name: like-fly-2, like-dot;\n animation-delay: .38s;\n}\n@keyframes like-swirl {\n from {\n rotate: 0deg;\n }\n to {\n rotate: 12deg;\n }\n}\n@keyframes like-fly {\n from {\n translate: 0 -20px;\n }\n to {\n translate: 0 -33px;\n }\n}\n@keyframes like-fly-2 {\n from {\n translate: 4.5px -18px;\n }\n to {\n translate: 8px -29px;\n }\n}\n@keyframes like-dot {\n 0% {\n scale: 0;\n animation-timing-function: cubic-bezier(.33, 1, .68, 1);\n }\n 24% {\n scale: 1;\n animation-timing-function: cubic-bezier(.32, 0, .67, 0);\n }\n 100% {\n scale: 0;\n }\n}\n\n/* the small hearts (like.js adds them): the rise is one unbroken ease-out, the sway a sine that rests at its turns,\n and size and fade keep their own clocks */\n.like-float {\n position: absolute;\n left: 0;\n top: -6px;\n opacity: 0;\n animation: like-rise var(--dur) cubic-bezier(.33, 1, .68, 1) var(--dl) both, like-fade var(--dur) var(--dl) both;\n}\n.like-float span {\n display: block;\n animation: like-sway var(--dur) cubic-bezier(.37, 0, .63, 1) var(--dl) both, like-grow var(--dur) var(--dl) both;\n}\n.like-float svg {\n display: block;\n width: var(--s);\n height: var(--s);\n margin: calc(var(--s) / -2) 0 0 calc(var(--s) / -2);\n fill: var(--fc);\n}\n@keyframes like-rise {\n from {\n translate: 0 0;\n }\n to {\n translate: 0 var(--dy);\n }\n}\n@keyframes like-fade {\n 0% {\n opacity: 0;\n animation-timing-function: cubic-bezier(.33, 1, .68, 1);\n }\n 14% {\n opacity: 1;\n animation-timing-function: linear;\n }\n 50% {\n opacity: 1;\n animation-timing-function: cubic-bezier(.37, 0, .63, 1);\n }\n 100% {\n opacity: 0;\n }\n}\n@keyframes like-sway {\n 0% {\n translate: 0 0;\n rotate: 0deg;\n }\n 33% {\n translate: var(--dx) 0;\n rotate: var(--r);\n }\n 70% {\n translate: calc(var(--dx) * -.6) 0;\n rotate: calc(var(--r) * -.6);\n }\n 100% {\n translate: calc(var(--dx) * .25) 0;\n rotate: 0deg;\n }\n}\n@keyframes like-grow {\n 0% {\n scale: .3;\n animation-timing-function: cubic-bezier(.34, 1.56, .64, 1);\n }\n 24% {\n scale: 1;\n animation-timing-function: cubic-bezier(.32, 0, .67, 0);\n }\n 100% {\n scale: .8;\n }\n}\n\n/* less motion: only the colour changes */\n@media (prefers-reduced-motion: reduce) {\n .like,\n .like * {\n animation: none !important;\n }\n}\n";
4
4
  const INNER = "<span class=\"like-fx\" aria-hidden=\"true\"><span class=\"like-ring\"></span><span class=\"like-dots\"><i><b></b><b></b></i><i><b></b><b></b></i><i><b></b><b></b></i><i><b></b><b></b></i><i><b></b><b></b></i><i><b></b><b></b></i><i><b></b><b></b></i></span></span><span class=\"like-icon\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"24\" height=\"24\"><path class=\"like-fill\" d=\"M19.0711 13.1421L13.4142 18.799C12.6332 19.58 11.3668 19.58 10.5858 18.799L4.92893 13.1421C2.97631 11.1895 2.97631 8.02369 4.92893 6.07107C6.88155 4.11845 10.0474 4.11845 12 6.07107C13.9526 4.11845 17.1184 4.11845 19.0711 6.07107C21.0237 8.02369 21.0237 11.1895 19.0711 13.1421Z\"></path><path class=\"like-line\" d=\"M19.0711 13.1421L13.4142 18.799C12.6332 19.58 11.3668 19.58 10.5858 18.799L4.92893 13.1421C2.97631 11.1895 2.97631 8.02369 4.92893 6.07107C6.88155 4.11845 10.0474 4.11845 12 6.07107C13.9526 4.11845 17.1184 4.11845 19.0711 6.07107C21.0237 8.02369 21.0237 11.1895 19.0711 13.1421Z\"></path></svg></span>";
5
5
  let api;
6
6
  function engine() {
7
7
  if (api) return api;
8
8
  const window = {};
9
- // Bursting heart · Craft Library, by Sara Lobo
9
+ // Bursting heart · Sara Lobo Library
10
10
  // A click on any <button class="like"> toggles aria-pressed; liking plays the burst (like.css) and sends 2 or 3
11
11
  // small hearts up. From your own code: setLike(button, true | false). Pass true as a third argument to set it
12
12
  // without the animation, for example when you restore a saved like as the page loads.
package/dist/magnet.js CHANGED
@@ -1,12 +1,12 @@
1
1
  // Magnetic icon · Craft Library, by Sara Lobo. magnet.css and magnet.js from the library, unchanged, around a mount.
2
2
  import { css } from './style.js';
3
- const CSS = "/* Magnetic icon · Craft Library, by Sara Lobo\n A dark disc with a brushed-metal rim and a search glyph (Figma: 40×40). Near the cursor the rim acts like metal\n near a strong magnet: the disc is drawn toward the pointer, the strained rim creases and trembles, its tip cracks\n into shards, and small voxels break off and fly to the pointer. Pairs with magnet.js.\n Everything is drawn in a 40-unit box, so --mag-size scales the whole piece, the pull included. */\n\n.mag {\n all: unset;\n position: relative;\n display: inline-grid;\n place-items: center;\n width: var(--mag-size, 40px);\n height: var(--mag-size, 40px);\n border-radius: 50%;\n color: #fff;\n cursor: pointer;\n -webkit-tap-highlight-color: transparent;\n transition: transform .2s cubic-bezier(.2, .8, .2, 1);\n}\n.mag:active {\n transform: scale(.94);\n}\n.mag:focus-visible {\n outline: 2px solid #9DB8FF;\n outline-offset: 4px;\n}\n/* the disc and its rim, redrawn by magnet.js; the stretch reaches past the button */\n.mag-ring {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n overflow: visible;\n pointer-events: none;\n}\n/* the shards and voxels: 46 units of room on every side of the 40-unit button (46 / 40 = 115%) */\n.mag-dust {\n position: absolute;\n left: -115%;\n top: -115%;\n width: 330%;\n height: 330%;\n pointer-events: none;\n}\n.mag-icon {\n position: relative;\n display: block;\n width: 50%;\n height: 50%;\n}\n";
3
+ const CSS = "/* Magnetic icon · Sara Lobo Library\n A dark disc with a brushed-metal rim and a search glyph (Figma: 40×40). Near the cursor the rim acts like metal\n near a strong magnet: the disc is drawn toward the pointer, the strained rim creases and trembles, its tip cracks\n into shards, and small voxels break off and fly to the pointer. Pairs with magnet.js.\n Everything is drawn in a 40-unit box, so --mag-size scales the whole piece, the pull included. */\n\n.mag {\n all: unset;\n position: relative;\n display: inline-grid;\n place-items: center;\n width: var(--mag-size, 40px);\n height: var(--mag-size, 40px);\n border-radius: 50%;\n color: #fff;\n cursor: pointer;\n -webkit-tap-highlight-color: transparent;\n transition: transform .2s cubic-bezier(.2, .8, .2, 1);\n}\n.mag:active {\n transform: scale(.94);\n}\n.mag:focus-visible {\n outline: 2px solid #9DB8FF;\n outline-offset: 4px;\n}\n/* the disc and its rim, redrawn by magnet.js; the stretch reaches past the button */\n.mag-ring {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n overflow: visible;\n pointer-events: none;\n}\n/* the shards and voxels: 46 units of room on every side of the 40-unit button (46 / 40 = 115%) */\n.mag-dust {\n position: absolute;\n left: -115%;\n top: -115%;\n width: 330%;\n height: 330%;\n pointer-events: none;\n}\n.mag-icon {\n position: relative;\n display: block;\n width: 50%;\n height: 50%;\n}\n";
4
4
  const MARKUP = "<svg class=\"mag-ring\" viewBox=\"0 0 40 40\" aria-hidden=\"true\"><defs><linearGradient id=\"magMetal\" x1=\"32\" y1=\"3.5\" x2=\"20\" y2=\"40\" gradientUnits=\"userSpaceOnUse\"><stop stop-color=\"#999\"></stop><stop offset=\".452\" stop-color=\"#fff\"></stop><stop offset=\"1\" stop-color=\"#A2A2A2\"></stop></linearGradient><radialGradient id=\"magFade\" cx=\"20\" cy=\"20\" r=\"40\" gradientUnits=\"userSpaceOnUse\"><stop offset=\".506\" stop-color=\"#fff\"></stop><stop offset=\".507\" stop-color=\"#000\"></stop></radialGradient><mask id=\"magMask\" maskUnits=\"userSpaceOnUse\" x=\"-30\" y=\"-30\" width=\"100\" height=\"100\"><rect x=\"-30\" y=\"-30\" width=\"100\" height=\"100\" fill=\"url(#magFade)\"></rect></mask></defs><path class=\"mag-rim\" mask=\"url(#magMask)\" d=\"M39.25 20C39.25 30.6315 30.6315 39.25 20 39.25C9.36852 39.25 .75 30.6315 .75 20C.75 9.36852 9.36852 .75 20 .75C30.6315 .75 39.25 9.36852 39.25 20Z\" fill=\"#fff\" fill-opacity=\".2\" stroke=\"url(#magMetal)\" stroke-width=\"1.5\"></path></svg><svg class=\"mag-icon\" viewBox=\"0 0 20 20\" fill=\"none\" aria-hidden=\"true\"><path d=\"M17.5 17.5L13.8807 13.8807M13.8807 13.8807C15.0871 12.6743 15.8333 11.0076 15.8333 9.16667C15.8333 5.48477 12.8486 2.5 9.16667 2.5C5.48477 2.5 2.5 5.48477 2.5 9.16667C2.5 12.8486 5.48477 15.8333 9.16667 15.8333C11.0076 15.8333 12.6743 15.0871 13.8807 13.8807Z\" stroke=\"currentColor\" stroke-width=\"1.66667\" stroke-linecap=\"round\" stroke-linejoin=\"round\"></path></svg>";
5
5
  let api;
6
6
  function engine() {
7
7
  if (api) return api;
8
8
  const window = {};
9
- // Magnetic icon · Craft Library, by Sara Lobo
9
+ // Magnetic icon · Sara Lobo Library
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
11
  // strained rim creases, trembles and comes apart where it is pulled, the tip cracks into shards, and voxels fly to
12
12
  // the pointer. Let go, and the rim springs back with a wobble, closing up as the loose voxels return to it. Pairs with
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,2 @@
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.5.0/dist/panorama/';
@@ -0,0 +1,22 @@
1
+ // Panorama: the mount around the library's panorama.js. With no video and no stills it shows the library's own clip
2
+ // and pictures, which ship with the package in dist/panorama/ (see panorama-assets.js for where they are read from).
3
+ import { mountPanorama } from './panorama.js';
4
+ import { ASSETS } from './panorama-assets.js';
5
+
6
+ // the pictures and their names, in the library demo's order
7
+ const FILMS = [
8
+ ['s01', 'The Coast Road', 2025], ['s02', 'Small Hours', 2024], ['s03', 'Vacancy', 2025], ['s13', 'Blue Room', 2024],
9
+ ['s05', 'Two Bodies', 2024], ['s06', 'Salt', 2025], ['s07', 'After Rain', 2023], ['s14', 'Keeper', 2025],
10
+ ['s08', 'High Country', 2025], ['s09', 'Last Picture House', 2024], ['s10', 'Mr. Albescu', 2023], ['s15', 'Summer, Ohio', 2023],
11
+ ['s11', 'Pinewood', 2025], ['s12', 'Riviera', 2026], ['s16', 'Room 414', 2026]
12
+ ];
13
+
14
+ // host: the element it fills (give it a size). o: { title, text, textItalic, video: { src, poster, title, year },
15
+ // stills: [{ src, title, year }] (up to 16), labels, intro, cursor, fonts }. Returns { replay(), destroy() }.
16
+ export function mountPanoramaReel(host, o = {}) {
17
+ const opts = {};
18
+ for (const k in o) if (o[k] !== undefined) opts[k] = o[k]; // an unset option keeps the piece's own default
19
+ if (opts.video === undefined) opts.video = { src: ASSETS + 'reel.mp4', poster: ASSETS + 'poster.webp', title: 'Wild Grass', year: 2026 };
20
+ if (opts.stills === undefined) opts.stills = FILMS.map(([n, title, year]) => ({ src: ASSETS + 't/' + n + '.webp', title, year }));
21
+ return mountPanorama(host, opts);
22
+ }