flipfolio 0.1.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/LICENSE +21 -0
- package/README.md +115 -0
- package/dist/chunk-DT6YOBGV.js +414 -0
- package/dist/chunk-DT6YOBGV.js.map +1 -0
- package/dist/element.cjs +524 -0
- package/dist/element.cjs.map +1 -0
- package/dist/element.d.ts +19 -0
- package/dist/element.js +109 -0
- package/dist/element.js.map +1 -0
- package/dist/folder-gallery.css +265 -0
- package/dist/index.cjs +421 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +56 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/react.cjs +532 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.ts +24 -0
- package/dist/react.js +108 -0
- package/dist/react.js.map +1 -0
- package/dist/vue.cjs +519 -0
- package/dist/vue.cjs.map +1 -0
- package/dist/vue.d.ts +39 -0
- package/dist/vue.js +105 -0
- package/dist/vue.js.map +1 -0
- package/package.json +81 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kirthi Balakrishnan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# flipfolio
|
|
2
|
+
|
|
3
|
+
A gallery where every item is a folder: stack them, grid them, spin them.
|
|
4
|
+
|
|
5
|
+
Started as a CSS 3D experiment; it now runs the [kirthi.studio](https://kirthi.studio) homepage. No framework, no dependencies.
|
|
6
|
+
|
|
7
|
+
Live demo: [kirthi.studio/demos/flipfolio](https://kirthi.studio/demos/flipfolio/)
|
|
8
|
+
|
|
9
|
+
> v0.1.0, an early release.
|
|
10
|
+
|
|
11
|
+

|
|
12
|
+
|
|
13
|
+
## Quick start
|
|
14
|
+
```sh
|
|
15
|
+
npm install flipfolio
|
|
16
|
+
```
|
|
17
|
+
```html
|
|
18
|
+
<link rel="stylesheet" href="flipfolio/styles.css">
|
|
19
|
+
<div id="gallery"></div>
|
|
20
|
+
<script type="module">
|
|
21
|
+
import { createFolderGallery } from 'flipfolio';
|
|
22
|
+
|
|
23
|
+
const gallery = createFolderGallery(document.getElementById('gallery'), {
|
|
24
|
+
items: [
|
|
25
|
+
{ label: 'One Corner', color: '#2a3a3a', src: '/img/one.jpg' },
|
|
26
|
+
{ label: 'Banglatown', color: '#3d2e42', content: '<p>Any HTML or a DOM node.</p>' },
|
|
27
|
+
{ label: 'Diversity', color: '#2a2d3e', src: '/img/three.jpg' },
|
|
28
|
+
],
|
|
29
|
+
mode: 'stack',
|
|
30
|
+
onSelect: (item, i) => console.log('selected', item.label, i),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// gallery.next() / .prev() / .goTo(2) / .setMode('carousel') / .destroy()
|
|
34
|
+
</script>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
React:
|
|
38
|
+
```jsx
|
|
39
|
+
import { FolderGallery } from 'flipfolio/react';
|
|
40
|
+
import 'flipfolio/styles.css';
|
|
41
|
+
|
|
42
|
+
<FolderGallery items={items} mode="grid" onSelect={(item, i) => open(item)} />
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Vue:
|
|
46
|
+
```vue
|
|
47
|
+
<script setup>
|
|
48
|
+
import { FolderGallery } from 'flipfolio/vue';
|
|
49
|
+
import 'flipfolio/styles.css';
|
|
50
|
+
</script>
|
|
51
|
+
|
|
52
|
+
<template>
|
|
53
|
+
<FolderGallery :items="items" mode="grid" @select="(item, i) => open(item)" />
|
|
54
|
+
</template>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Web component:
|
|
58
|
+
```html
|
|
59
|
+
<script type="module">
|
|
60
|
+
import { defineFolderGallery } from 'flipfolio/element';
|
|
61
|
+
defineFolderGallery();
|
|
62
|
+
</script>
|
|
63
|
+
<folder-gallery mode="carousel"></folder-gallery>
|
|
64
|
+
<script>document.querySelector('folder-gallery').items = items;</script>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## What it looks like
|
|
68
|
+
|
|
69
|
+

|
|
70
|
+
Folders hold your actual content. These are live project images, loaded with `item.src`.
|
|
71
|
+
|
|
72
|
+

|
|
73
|
+
One items array, three layouts. `setMode()` animates between them at runtime.
|
|
74
|
+
|
|
75
|
+

|
|
76
|
+
Interiors take images, markup, DOM nodes, or a `contentRenderer` for anything else.
|
|
77
|
+
|
|
78
|
+

|
|
79
|
+
Per-folder tab colors, tokens for radius and blur, and the silhouette itself is one SVG path.
|
|
80
|
+
|
|
81
|
+
## API
|
|
82
|
+
`createFolderGallery(rootElement, options) -> handle`
|
|
83
|
+
|
|
84
|
+
| option | type | default | notes |
|
|
85
|
+
|---|---|---|---|
|
|
86
|
+
| `items` | `Item[]` | `[]` | `{ label?, color?, src?, content?, decal?, ...data }` |
|
|
87
|
+
| `mode` | `'stack'\|'grid'\|'carousel'` | `'stack'` | 3 modes (a 4th "shelf" is planned) |
|
|
88
|
+
| `peek` | `'hover'\|'always'\|'off'` | `'hover'` | contents slide out of the folder |
|
|
89
|
+
| `contentRenderer` | `(card, item, i) => void` | built-in | fills the folder interior with whatever you render |
|
|
90
|
+
| `onSelect` | `(item, i) => void` | (none) | fired on click/Enter of the active folder (no built-in navigation) |
|
|
91
|
+
| `folderPath` | `string` | manila default | SVG path `d` (viewBox `0 0 480 342`) |
|
|
92
|
+
| `loop` | `boolean` | `true` | wrap-around |
|
|
93
|
+
| `scrollNav` | `boolean` | `true` | wheel/trackpad cycles the stack/carousel |
|
|
94
|
+
| `reducedMotion` | `'auto'\|'off'\|'force'` | `'auto'` | drops the 3D tilt when reduced |
|
|
95
|
+
| `defaultActiveIndex` | `number` | `0` | |
|
|
96
|
+
| `label` | `string` | `'Folder gallery'` | `aria-label` for the listbox |
|
|
97
|
+
|
|
98
|
+
Handle: `next()`, `prev()`, `goTo(i)`, `setMode(m)`, `setPeek(p)`, `getActiveIndex()`, `getMode()`, `destroy()`.
|
|
99
|
+
|
|
100
|
+
Events (CustomEvent on the root, bubbling): `fg-select`, `fg-activechange`, `fg-modechange`, each with `event.detail`.
|
|
101
|
+
|
|
102
|
+
## Content
|
|
103
|
+
Each folder renders what you give it: `item.src` for an image, `item.content` for an HTML string or a DOM node, or a `contentRenderer(card, item, index)` for anything richer. Set `item.decal` to an image URL and the photo prints on the folder's front panel; the back and tab keep their color. Contents slide out of the folder on hover; the `peek` option makes that always-on or turns it off.
|
|
104
|
+
|
|
105
|
+
## Accessibility
|
|
106
|
+
`role="listbox"`/`option`, roving tabindex, arrow-key + Home/End navigation, `aria-live` position announcements, `:focus-visible` rings, and a `prefers-reduced-motion` fallback that drops the 3D tilt.
|
|
107
|
+
|
|
108
|
+
## Theming
|
|
109
|
+
Override the CSS custom properties on `.fg-root`: `--fg-folder-bg`, `--fg-radius`, `--fg-perspective`, `--fg-ease`, `--fg-front`, `--fg-front-solid`, `--fg-active-blur`, `--fg-label`, `--fg-dot`, `--fg-dot-active`. Light is the default; dark applies automatically under `prefers-color-scheme: dark`, and `data-fg-theme="light"` or `"dark"` on `.fg-root` overrides either way.
|
|
110
|
+
|
|
111
|
+
## Limits
|
|
112
|
+
Client-rendered only: folders paint after JS runs, there is no SSR of the layout itself. Every folder is a live 3D layer, so the sweet spot is 12 items or fewer, not hundreds. Uses `aspect-ratio` and `backdrop-filter`: evergreen browsers only, no IE fallback.
|
|
113
|
+
|
|
114
|
+
## License
|
|
115
|
+
MIT © Kirthi Balakrishnan
|
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
3
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
4
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
5
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
6
|
+
var __spreadValues = (a, b) => {
|
|
7
|
+
for (var prop in b || (b = {}))
|
|
8
|
+
if (__hasOwnProp.call(b, prop))
|
|
9
|
+
__defNormalProp(a, prop, b[prop]);
|
|
10
|
+
if (__getOwnPropSymbols)
|
|
11
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
12
|
+
if (__propIsEnum.call(b, prop))
|
|
13
|
+
__defNormalProp(a, prop, b[prop]);
|
|
14
|
+
}
|
|
15
|
+
return a;
|
|
16
|
+
};
|
|
17
|
+
var __objRest = (source, exclude) => {
|
|
18
|
+
var target = {};
|
|
19
|
+
for (var prop in source)
|
|
20
|
+
if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
|
|
21
|
+
target[prop] = source[prop];
|
|
22
|
+
if (source != null && __getOwnPropSymbols)
|
|
23
|
+
for (var prop of __getOwnPropSymbols(source)) {
|
|
24
|
+
if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
|
|
25
|
+
target[prop] = source[prop];
|
|
26
|
+
}
|
|
27
|
+
return target;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// src/folder-gallery.js
|
|
31
|
+
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
32
|
+
var DEFAULT_FOLDER_PATH = "M 0 22 C 0 10 10 0 22 0 L 155 0 C 168 0 172 6 176 14 C 180 24 188 32 204 32 L 458 32 C 470 32 480 42 480 54 L 480 320 C 480 332 470 342 458 342 L 22 342 C 10 342 0 332 0 320 Z";
|
|
33
|
+
var DEFAULTS = {
|
|
34
|
+
mode: "stack",
|
|
35
|
+
// 'stack' | 'grid' | 'carousel'
|
|
36
|
+
folderPath: DEFAULT_FOLDER_PATH,
|
|
37
|
+
loop: true,
|
|
38
|
+
scrollNav: true,
|
|
39
|
+
reducedMotion: "auto",
|
|
40
|
+
// 'auto' | 'off' | 'force'
|
|
41
|
+
peek: "hover",
|
|
42
|
+
// 'hover' | 'always' | 'off'
|
|
43
|
+
defaultActiveIndex: 0,
|
|
44
|
+
label: "Folder gallery"
|
|
45
|
+
};
|
|
46
|
+
var PEEK_MODES = /* @__PURE__ */ new Set(["hover", "always", "off"]);
|
|
47
|
+
var MODE_WIDTHS = { stack: 480, grid: 720, carousel: 720 };
|
|
48
|
+
var SCROLL_THRESHOLD = 30;
|
|
49
|
+
var SCROLL_COOLDOWN = { stack: 450, carousel: 300 };
|
|
50
|
+
function hexToRgb(hex) {
|
|
51
|
+
const h = String(hex).replace("#", "");
|
|
52
|
+
return [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16)];
|
|
53
|
+
}
|
|
54
|
+
function folderColors(hex) {
|
|
55
|
+
const [r, g, b] = hexToRgb(hex);
|
|
56
|
+
const dn = (v) => Math.max(v - 30, 0);
|
|
57
|
+
const up = (v, o) => Math.min(v + o, 255);
|
|
58
|
+
const fr = up(r, 35);
|
|
59
|
+
const fg = up(g, 35);
|
|
60
|
+
const fb = up(b, 35);
|
|
61
|
+
const yiq = (fr * 299 + fg * 587 + fb * 114) / 1e3;
|
|
62
|
+
return {
|
|
63
|
+
back: `rgb(${dn(r)},${dn(g)},${dn(b)})`,
|
|
64
|
+
front: `rgba(${up(r, 40)},${up(g, 40)},${up(b, 40)},0.55)`,
|
|
65
|
+
frontSolid: `rgb(${fr},${fg},${fb})`,
|
|
66
|
+
label: yiq >= 128 ? "#1c1c1a" : "#ffffff"
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function defaultContentRenderer(card, item) {
|
|
70
|
+
const wrap = document.createElement("div");
|
|
71
|
+
wrap.className = "fg-content";
|
|
72
|
+
if (item.content && typeof item.content === "object" && typeof item.content.nodeType === "number") {
|
|
73
|
+
wrap.appendChild(item.content);
|
|
74
|
+
} else if (typeof item.content === "string") {
|
|
75
|
+
wrap.innerHTML = item.content;
|
|
76
|
+
} else if (item.src) {
|
|
77
|
+
const img = document.createElement("img");
|
|
78
|
+
img.className = "fg-image";
|
|
79
|
+
img.src = item.src;
|
|
80
|
+
img.alt = item.label || "";
|
|
81
|
+
img.loading = "lazy";
|
|
82
|
+
img.decoding = "async";
|
|
83
|
+
wrap.appendChild(img);
|
|
84
|
+
}
|
|
85
|
+
card.appendChild(wrap);
|
|
86
|
+
}
|
|
87
|
+
function createFolderGallery(root, options = {}) {
|
|
88
|
+
if (!root || !root.nodeType) throw new Error("createFolderGallery: a root element is required");
|
|
89
|
+
const opts = __spreadValues(__spreadValues({}, DEFAULTS), options);
|
|
90
|
+
const items = Array.isArray(opts.items) ? opts.items : [];
|
|
91
|
+
const n = items.length;
|
|
92
|
+
const renderContent = typeof opts.contentRenderer === "function" ? opts.contentRenderer : defaultContentRenderer;
|
|
93
|
+
const reduced = opts.reducedMotion === "force" || opts.reducedMotion !== "off" && typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
94
|
+
let mode = MODE_WIDTHS[opts.mode] ? opts.mode : "stack";
|
|
95
|
+
let active = Math.min(Math.max(opts.defaultActiveIndex | 0, 0), Math.max(n - 1, 0));
|
|
96
|
+
let scrollCooldown = false;
|
|
97
|
+
let scrollAccum = 0;
|
|
98
|
+
const cleanups = [];
|
|
99
|
+
const on = (el, ev, fn, o) => {
|
|
100
|
+
el.addEventListener(ev, fn, o);
|
|
101
|
+
cleanups.push(() => el.removeEventListener(ev, fn, o));
|
|
102
|
+
};
|
|
103
|
+
const emit = (name, detail) => root.dispatchEvent(new CustomEvent(name, { detail, bubbles: true }));
|
|
104
|
+
root.classList.add("fg-root");
|
|
105
|
+
root.setAttribute("data-fg-mode", mode);
|
|
106
|
+
root.setAttribute("data-fg-peek", PEEK_MODES.has(opts.peek) ? opts.peek : "hover");
|
|
107
|
+
const scene = document.createElement("div");
|
|
108
|
+
scene.className = "fg-scene";
|
|
109
|
+
scene.setAttribute("role", "listbox");
|
|
110
|
+
scene.setAttribute("aria-label", opts.label);
|
|
111
|
+
scene.setAttribute("aria-roledescription", "folder gallery");
|
|
112
|
+
const dotsEl = document.createElement("div");
|
|
113
|
+
dotsEl.className = "fg-dots";
|
|
114
|
+
dotsEl.setAttribute("role", "tablist");
|
|
115
|
+
const live = document.createElement("div");
|
|
116
|
+
live.className = "fg-sr-only";
|
|
117
|
+
live.setAttribute("aria-live", "polite");
|
|
118
|
+
live.setAttribute("aria-atomic", "true");
|
|
119
|
+
root.append(scene, dotsEl, live);
|
|
120
|
+
function teardown() {
|
|
121
|
+
cleanups.forEach((fn) => fn());
|
|
122
|
+
cleanups.length = 0;
|
|
123
|
+
root.replaceChildren();
|
|
124
|
+
root.classList.remove("fg-root");
|
|
125
|
+
root.removeAttribute("data-fg-mode");
|
|
126
|
+
root.removeAttribute("data-fg-peek");
|
|
127
|
+
}
|
|
128
|
+
if (n === 0) {
|
|
129
|
+
return { next() {
|
|
130
|
+
}, prev() {
|
|
131
|
+
}, goTo() {
|
|
132
|
+
}, setMode() {
|
|
133
|
+
}, setPeek() {
|
|
134
|
+
}, getActiveIndex: () => -1, getMode: () => mode, destroy: teardown };
|
|
135
|
+
}
|
|
136
|
+
function setCardTransform(card, { x, y, z, rx, ry, s, zIndex, opacity, isActive }) {
|
|
137
|
+
card.style.transform = `translate3d(${x}px,${y}px,${z}px) rotateX(${rx}deg) rotateY(${ry}deg) scale(${s})`;
|
|
138
|
+
card.style.transformOrigin = "center bottom";
|
|
139
|
+
card.style.zIndex = String(zIndex);
|
|
140
|
+
card.style.opacity = String(opacity);
|
|
141
|
+
card.setAttribute("aria-selected", isActive ? "true" : "false");
|
|
142
|
+
card.classList.toggle("is-active", isActive);
|
|
143
|
+
}
|
|
144
|
+
const cardEls = items.map((item, i) => {
|
|
145
|
+
const card = document.createElement("div");
|
|
146
|
+
card.className = "fg-card";
|
|
147
|
+
card.tabIndex = i === active ? 0 : -1;
|
|
148
|
+
card.setAttribute("role", "option");
|
|
149
|
+
card.setAttribute("aria-label", item.label || `Item ${i + 1}`);
|
|
150
|
+
if (item.color) {
|
|
151
|
+
const c = folderColors(item.color);
|
|
152
|
+
card.style.setProperty("--fg-folder-bg", item.color);
|
|
153
|
+
card.style.setProperty("--fg-front", c.front);
|
|
154
|
+
card.style.setProperty("--fg-front-solid", c.frontSolid);
|
|
155
|
+
card.style.setProperty("--fg-label-on-color", c.label);
|
|
156
|
+
}
|
|
157
|
+
const svg = document.createElementNS(SVG_NS, "svg");
|
|
158
|
+
svg.setAttribute("class", "fg-folder");
|
|
159
|
+
svg.setAttribute("viewBox", "0 0 480 342");
|
|
160
|
+
svg.setAttribute("preserveAspectRatio", "none");
|
|
161
|
+
svg.setAttribute("aria-hidden", "true");
|
|
162
|
+
const path = document.createElementNS(SVG_NS, "path");
|
|
163
|
+
path.setAttribute("d", opts.folderPath);
|
|
164
|
+
path.setAttribute("fill", item.color ? folderColors(item.color).back : "var(--fg-folder-bg)");
|
|
165
|
+
svg.appendChild(path);
|
|
166
|
+
card.appendChild(svg);
|
|
167
|
+
renderContent(card, item, i);
|
|
168
|
+
const front = document.createElement("div");
|
|
169
|
+
front.className = "fg-front";
|
|
170
|
+
if (item.decal) {
|
|
171
|
+
card.classList.add("fg-card--decal");
|
|
172
|
+
const photo = document.createElement("img");
|
|
173
|
+
photo.className = "fg-decal";
|
|
174
|
+
photo.src = item.decal;
|
|
175
|
+
photo.alt = "";
|
|
176
|
+
photo.loading = "lazy";
|
|
177
|
+
photo.decoding = "async";
|
|
178
|
+
front.appendChild(photo);
|
|
179
|
+
}
|
|
180
|
+
if (item.label) {
|
|
181
|
+
const label = document.createElement("div");
|
|
182
|
+
label.className = "fg-label";
|
|
183
|
+
label.textContent = item.label;
|
|
184
|
+
front.appendChild(label);
|
|
185
|
+
}
|
|
186
|
+
card.appendChild(front);
|
|
187
|
+
scene.appendChild(card);
|
|
188
|
+
return card;
|
|
189
|
+
});
|
|
190
|
+
const dots = cardEls.map((_, i) => {
|
|
191
|
+
const dot = document.createElement("button");
|
|
192
|
+
dot.type = "button";
|
|
193
|
+
dot.className = "fg-dot";
|
|
194
|
+
dot.setAttribute("role", "tab");
|
|
195
|
+
dot.setAttribute("aria-label", items[i].label || `Go to ${i + 1}`);
|
|
196
|
+
on(dot, "click", () => goTo(i));
|
|
197
|
+
dotsEl.appendChild(dot);
|
|
198
|
+
return dot;
|
|
199
|
+
});
|
|
200
|
+
function layoutStack() {
|
|
201
|
+
scene.style.height = "";
|
|
202
|
+
cardEls.forEach((card, i) => {
|
|
203
|
+
const behind = (i - active + n) % n;
|
|
204
|
+
if (behind === 0) {
|
|
205
|
+
setCardTransform(card, { x: 0, y: 100, z: 20, rx: 0, ry: 0, s: 1, zIndex: n + 1, opacity: 1, isActive: true });
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
const safeN = Math.max(n - 1, 1);
|
|
209
|
+
const tz = -behind * 25 - behind * behind * 3;
|
|
210
|
+
const rx = reduced ? 0 : -behind * 3;
|
|
211
|
+
const ty = 100 - behind * 28 - behind * behind * 2;
|
|
212
|
+
const sc = 1 - behind * 0.06 - behind / safeN * (behind / safeN) * 0.04;
|
|
213
|
+
setCardTransform(card, { x: 0, y: ty, z: tz, rx, ry: 0, s: Math.max(sc, 0.5), zIndex: n - behind, opacity: 1 - behind * 0.08, isActive: false });
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
function layoutGrid() {
|
|
217
|
+
const sceneW = scene.clientWidth || MODE_WIDTHS.grid;
|
|
218
|
+
const cols = sceneW < 520 ? 2 : n <= 4 ? 2 : 3;
|
|
219
|
+
const gap = 16;
|
|
220
|
+
const cardW = sceneW;
|
|
221
|
+
const cardH = sceneW * (2 / 3);
|
|
222
|
+
const sc = (sceneW - (cols - 1) * gap) / (cols * cardW);
|
|
223
|
+
const scaledW = cardW * sc;
|
|
224
|
+
const scaledH = cardH * sc;
|
|
225
|
+
const tabScaled = 22 * sc;
|
|
226
|
+
const rows = Math.ceil(n / cols);
|
|
227
|
+
cardEls.forEach((card, i) => {
|
|
228
|
+
const col = i % cols;
|
|
229
|
+
const row = Math.floor(i / cols);
|
|
230
|
+
const itemsInRow = Math.min(cols, n - row * cols);
|
|
231
|
+
const rowW = itemsInRow * scaledW + (itemsInRow - 1) * gap;
|
|
232
|
+
const rowStartX = (sceneW - rowW) / 2;
|
|
233
|
+
const cellLeft = rowStartX + col * (scaledW + gap);
|
|
234
|
+
const cellTop = row * (scaledH + tabScaled + gap) + tabScaled;
|
|
235
|
+
const tx = cellLeft + scaledW / 2 - cardW / 2;
|
|
236
|
+
const ty = cellTop + scaledH - cardH;
|
|
237
|
+
setCardTransform(card, { x: tx, y: ty, z: 0, rx: 0, ry: 0, s: sc, zIndex: i === active ? 2 : 1, opacity: 1, isActive: i === active });
|
|
238
|
+
});
|
|
239
|
+
scene.style.height = rows * (scaledH + tabScaled + gap) + 20 + "px";
|
|
240
|
+
}
|
|
241
|
+
function layoutCarousel() {
|
|
242
|
+
const sceneW = scene.clientWidth || MODE_WIDTHS.carousel;
|
|
243
|
+
const cardW = sceneW;
|
|
244
|
+
const cardH = cardW * (2 / 3);
|
|
245
|
+
const activeSc = 0.62;
|
|
246
|
+
const activeW = cardW * activeSc;
|
|
247
|
+
const sceneH = Math.max(cardH * activeSc + 40, 180);
|
|
248
|
+
scene.style.height = sceneH + "px";
|
|
249
|
+
const offsetStep = Math.min(130, (sceneW - activeW) / 4);
|
|
250
|
+
cardEls.forEach((card, i) => {
|
|
251
|
+
let offset = i - active;
|
|
252
|
+
if (n > 2 && offset > Math.floor(n / 2)) offset -= n;
|
|
253
|
+
if (n > 2 && offset < -Math.floor(n / 2)) offset += n;
|
|
254
|
+
const sc = Math.max(activeSc - Math.abs(offset) * 0.06, 0.28);
|
|
255
|
+
const z = -Math.abs(offset) * 50;
|
|
256
|
+
const op = Math.max(1 - Math.abs(offset) * 0.25, 0.15);
|
|
257
|
+
const tx = sceneW / 2 + offset * offsetStep - cardW / 2;
|
|
258
|
+
const ty = sceneH - 20 - cardH;
|
|
259
|
+
setCardTransform(card, { x: tx, y: ty, z, rx: 0, ry: 0, s: sc, zIndex: n - Math.abs(offset), opacity: op, isActive: offset === 0 });
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
function sizeScene() {
|
|
263
|
+
scene.style.width = `min(${MODE_WIDTHS[mode]}px, 92vw)`;
|
|
264
|
+
}
|
|
265
|
+
function applyLayout() {
|
|
266
|
+
sizeScene();
|
|
267
|
+
(mode === "grid" ? layoutGrid : mode === "carousel" ? layoutCarousel : layoutStack)();
|
|
268
|
+
dots.forEach((d, i) => {
|
|
269
|
+
d.classList.toggle("is-active", i === active);
|
|
270
|
+
d.setAttribute("aria-selected", i === active ? "true" : "false");
|
|
271
|
+
});
|
|
272
|
+
cardEls.forEach((c, i) => {
|
|
273
|
+
c.tabIndex = i === active ? 0 : -1;
|
|
274
|
+
});
|
|
275
|
+
const cur = items[active];
|
|
276
|
+
live.textContent = cur && cur.label ? `${active + 1} of ${n}: ${cur.label}` : `${active + 1} of ${n}`;
|
|
277
|
+
}
|
|
278
|
+
function goTo(i) {
|
|
279
|
+
const next = opts.loop ? (i % n + n) % n : Math.min(Math.max(i, 0), n - 1);
|
|
280
|
+
if (next === active) return;
|
|
281
|
+
active = next;
|
|
282
|
+
applyLayout();
|
|
283
|
+
emit("fg-activechange", { index: active, item: items[active] });
|
|
284
|
+
}
|
|
285
|
+
function select(i) {
|
|
286
|
+
emit("fg-select", { index: i, item: items[i] });
|
|
287
|
+
if (typeof opts.onSelect === "function") opts.onSelect(items[i], i);
|
|
288
|
+
}
|
|
289
|
+
function setMode(m) {
|
|
290
|
+
if (m === mode || !MODE_WIDTHS[m]) return;
|
|
291
|
+
mode = m;
|
|
292
|
+
root.setAttribute("data-fg-mode", mode);
|
|
293
|
+
applyLayout();
|
|
294
|
+
emit("fg-modechange", { mode });
|
|
295
|
+
}
|
|
296
|
+
function setPeek(p) {
|
|
297
|
+
if (!PEEK_MODES.has(p)) return;
|
|
298
|
+
root.setAttribute("data-fg-peek", p);
|
|
299
|
+
}
|
|
300
|
+
cardEls.forEach((card, i) => {
|
|
301
|
+
on(card, "click", () => {
|
|
302
|
+
i === active ? select(i) : goTo(i);
|
|
303
|
+
});
|
|
304
|
+
on(card, "keydown", (e) => {
|
|
305
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
306
|
+
e.preventDefault();
|
|
307
|
+
select(i);
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
function handleArrowKeys(e) {
|
|
312
|
+
if (e.key === "ArrowRight" || e.key === "ArrowDown") {
|
|
313
|
+
e.preventDefault();
|
|
314
|
+
goTo(active + 1);
|
|
315
|
+
return true;
|
|
316
|
+
}
|
|
317
|
+
if (e.key === "ArrowLeft" || e.key === "ArrowUp") {
|
|
318
|
+
e.preventDefault();
|
|
319
|
+
goTo(active - 1);
|
|
320
|
+
return true;
|
|
321
|
+
}
|
|
322
|
+
if (e.key === "Home") {
|
|
323
|
+
e.preventDefault();
|
|
324
|
+
goTo(0);
|
|
325
|
+
return true;
|
|
326
|
+
}
|
|
327
|
+
if (e.key === "End") {
|
|
328
|
+
e.preventDefault();
|
|
329
|
+
goTo(n - 1);
|
|
330
|
+
return true;
|
|
331
|
+
}
|
|
332
|
+
return false;
|
|
333
|
+
}
|
|
334
|
+
on(scene, "keydown", (e) => {
|
|
335
|
+
if (handleArrowKeys(e)) cardEls[active].focus();
|
|
336
|
+
});
|
|
337
|
+
on(dotsEl, "keydown", (e) => {
|
|
338
|
+
if (handleArrowKeys(e)) cardEls[active].focus();
|
|
339
|
+
});
|
|
340
|
+
let hovered = false;
|
|
341
|
+
on(root, "mouseenter", () => {
|
|
342
|
+
hovered = true;
|
|
343
|
+
});
|
|
344
|
+
on(root, "mouseleave", () => {
|
|
345
|
+
hovered = false;
|
|
346
|
+
});
|
|
347
|
+
on(document, "keydown", (e) => {
|
|
348
|
+
if (!hovered) return;
|
|
349
|
+
const ae = document.activeElement;
|
|
350
|
+
if (ae && ae !== document.body && !root.contains(ae)) return;
|
|
351
|
+
handleArrowKeys(e);
|
|
352
|
+
});
|
|
353
|
+
if (opts.scrollNav) {
|
|
354
|
+
on(root, "wheel", (e) => {
|
|
355
|
+
if (mode === "grid") return;
|
|
356
|
+
const atStart = !opts.loop && active === 0 && e.deltaY < 0;
|
|
357
|
+
const atEnd = !opts.loop && active === n - 1 && e.deltaY > 0;
|
|
358
|
+
if (atStart || atEnd) return;
|
|
359
|
+
e.preventDefault();
|
|
360
|
+
if (scrollCooldown) return;
|
|
361
|
+
scrollAccum += e.deltaY;
|
|
362
|
+
if (Math.abs(scrollAccum) >= SCROLL_THRESHOLD) {
|
|
363
|
+
const dir = scrollAccum > 0 ? 1 : -1;
|
|
364
|
+
scrollAccum = 0;
|
|
365
|
+
scrollCooldown = true;
|
|
366
|
+
goTo(active + dir);
|
|
367
|
+
setTimeout(() => {
|
|
368
|
+
scrollCooldown = false;
|
|
369
|
+
scrollAccum = 0;
|
|
370
|
+
}, SCROLL_COOLDOWN[mode] || 300);
|
|
371
|
+
}
|
|
372
|
+
}, { passive: false });
|
|
373
|
+
}
|
|
374
|
+
let touchStartX = 0;
|
|
375
|
+
let touchStartY = 0;
|
|
376
|
+
on(scene, "touchstart", (e) => {
|
|
377
|
+
touchStartX = e.touches[0].clientX;
|
|
378
|
+
touchStartY = e.touches[0].clientY;
|
|
379
|
+
}, { passive: true });
|
|
380
|
+
on(scene, "touchend", (e) => {
|
|
381
|
+
const dx = touchStartX - e.changedTouches[0].clientX;
|
|
382
|
+
const dy = touchStartY - e.changedTouches[0].clientY;
|
|
383
|
+
const ax = Math.abs(dx);
|
|
384
|
+
const ay = Math.abs(dy);
|
|
385
|
+
if (Math.max(ax, ay) < 40) return;
|
|
386
|
+
const dir = ax > ay ? dx > 0 ? 1 : -1 : dy > 0 ? 1 : -1;
|
|
387
|
+
goTo(active + dir);
|
|
388
|
+
}, { passive: true });
|
|
389
|
+
let resizeTimer;
|
|
390
|
+
on(window, "resize", () => {
|
|
391
|
+
clearTimeout(resizeTimer);
|
|
392
|
+
resizeTimer = setTimeout(applyLayout, 150);
|
|
393
|
+
});
|
|
394
|
+
applyLayout();
|
|
395
|
+
return {
|
|
396
|
+
next: () => goTo(active + 1),
|
|
397
|
+
prev: () => goTo(active - 1),
|
|
398
|
+
goTo,
|
|
399
|
+
setMode,
|
|
400
|
+
setPeek,
|
|
401
|
+
getActiveIndex: () => active,
|
|
402
|
+
getMode: () => mode,
|
|
403
|
+
destroy: teardown
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
var folder_gallery_default = createFolderGallery;
|
|
407
|
+
|
|
408
|
+
export {
|
|
409
|
+
__spreadValues,
|
|
410
|
+
__objRest,
|
|
411
|
+
createFolderGallery,
|
|
412
|
+
folder_gallery_default
|
|
413
|
+
};
|
|
414
|
+
//# sourceMappingURL=chunk-DT6YOBGV.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/folder-gallery.js"],"sourcesContent":["/* ============================================================================\n * folder-gallery - a framework-agnostic 3D folder gallery (core, v0.1.0, WIP)\n *\n * import { createFolderGallery } from 'folder-gallery';\n * import 'folder-gallery/styles.css';\n *\n * const gallery = createFolderGallery(rootEl, {\n * items: [{ label, color, src }, ...], // or { content: Node | html }\n * mode: 'stack', // 'stack' | 'grid' | 'carousel'\n * onSelect: (item, index) => { ... },\n * });\n * gallery.next(); gallery.setMode('grid'); gallery.destroy();\n *\n * Ported from a hand-built portfolio engine. No framework, no dependencies.\n * Owns its own DOM and removes every listener on destroy().\n * ========================================================================== */\n\nconst SVG_NS = 'http://www.w3.org/2000/svg';\n\n/* Default manila-folder-with-tab silhouette (viewBox 0 0 480 342). */\nconst DEFAULT_FOLDER_PATH =\n 'M 0 22 C 0 10 10 0 22 0 L 155 0 C 168 0 172 6 176 14 C 180 24 188 32 204 32 ' +\n 'L 458 32 C 470 32 480 42 480 54 L 480 320 C 480 332 470 342 458 342 ' +\n 'L 22 342 C 10 342 0 332 0 320 Z';\n\nconst DEFAULTS = {\n mode: 'stack', // 'stack' | 'grid' | 'carousel'\n folderPath: DEFAULT_FOLDER_PATH,\n loop: true,\n scrollNav: true,\n reducedMotion: 'auto', // 'auto' | 'off' | 'force'\n peek: 'hover', // 'hover' | 'always' | 'off'\n defaultActiveIndex: 0,\n label: 'Folder gallery',\n};\n\nconst PEEK_MODES = new Set(['hover', 'always', 'off']);\n\nconst MODE_WIDTHS = { stack: 480, grid: 720, carousel: 720 };\nconst SCROLL_THRESHOLD = 30;\nconst SCROLL_COOLDOWN = { stack: 450, carousel: 300 };\n\nfunction hexToRgb(hex) {\n const h = String(hex).replace('#', '');\n return [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16)];\n}\n/* Derive the folder's three surfaces from one base color (same offsets as the\n * original engine): back/tab −30, frosted front +40 at 0.55, solid front +35.\n * The solid variant is what keeps NON-active cards fully opaque - only the\n * active card gets the translucent frosted treatment. Also picks a label\n * color (white or ink) by the frontSolid surface's YIQ brightness, so a\n * dark folder color doesn't leave the label unreadable regardless of theme. */\nfunction folderColors(hex) {\n const [r, g, b] = hexToRgb(hex);\n const dn = (v) => Math.max(v - 30, 0);\n const up = (v, o) => Math.min(v + o, 255);\n const fr = up(r, 35);\n const fg = up(g, 35);\n const fb = up(b, 35);\n const yiq = (fr * 299 + fg * 587 + fb * 114) / 1000;\n return {\n back: `rgb(${dn(r)},${dn(g)},${dn(b)})`,\n front: `rgba(${up(r, 40)},${up(g, 40)},${up(b, 40)},0.55)`,\n frontSolid: `rgb(${fr},${fg},${fb})`,\n label: yiq >= 128 ? '#1c1c1a' : '#ffffff',\n };\n}\n\n/* Built-in default content renderer. Accepts item.content (a Node or HTML\n * string) or item.src (an image). Consumers pass their own contentRenderer\n * (card, item, index) to hold arbitrary content - that's the whole point. */\nfunction defaultContentRenderer(card, item /* , index */) {\n const wrap = document.createElement('div');\n wrap.className = 'fg-content';\n // Duck-type DOM nodes (nodeType) rather than `instanceof Node` - `Node`\n // isn't a global in every embedding (SSR shims, minimal DOM harnesses).\n if (item.content && typeof item.content === 'object' && typeof item.content.nodeType === 'number') {\n wrap.appendChild(item.content);\n } else if (typeof item.content === 'string') {\n wrap.innerHTML = item.content;\n } else if (item.src) {\n const img = document.createElement('img');\n img.className = 'fg-image';\n img.src = item.src;\n img.alt = item.label || '';\n img.loading = 'lazy';\n img.decoding = 'async';\n wrap.appendChild(img);\n }\n card.appendChild(wrap);\n}\n\nexport function createFolderGallery(root, options = {}) {\n if (!root || !root.nodeType) throw new Error('createFolderGallery: a root element is required');\n\n const opts = { ...DEFAULTS, ...options };\n const items = Array.isArray(opts.items) ? opts.items : [];\n const n = items.length;\n const renderContent =\n typeof opts.contentRenderer === 'function' ? opts.contentRenderer : defaultContentRenderer;\n\n const reduced =\n opts.reducedMotion === 'force' ||\n (opts.reducedMotion !== 'off' &&\n typeof matchMedia === 'function' &&\n matchMedia('(prefers-reduced-motion: reduce)').matches);\n\n let mode = MODE_WIDTHS[opts.mode] ? opts.mode : 'stack';\n let active = Math.min(Math.max(opts.defaultActiveIndex | 0, 0), Math.max(n - 1, 0));\n let scrollCooldown = false;\n let scrollAccum = 0;\n\n /* ── Listener bookkeeping for destroy() ── */\n const cleanups = [];\n const on = (el, ev, fn, o) => { el.addEventListener(ev, fn, o); cleanups.push(() => el.removeEventListener(ev, fn, o)); };\n const emit = (name, detail) => root.dispatchEvent(new CustomEvent(name, { detail, bubbles: true }));\n\n /* ── Build own DOM (no page scaffold assumed) ── */\n root.classList.add('fg-root');\n root.setAttribute('data-fg-mode', mode);\n root.setAttribute('data-fg-peek', PEEK_MODES.has(opts.peek) ? opts.peek : 'hover');\n const scene = document.createElement('div');\n scene.className = 'fg-scene';\n scene.setAttribute('role', 'listbox');\n scene.setAttribute('aria-label', opts.label);\n scene.setAttribute('aria-roledescription', 'folder gallery');\n const dotsEl = document.createElement('div');\n dotsEl.className = 'fg-dots';\n dotsEl.setAttribute('role', 'tablist');\n const live = document.createElement('div');\n live.className = 'fg-sr-only';\n live.setAttribute('aria-live', 'polite');\n live.setAttribute('aria-atomic', 'true');\n root.append(scene, dotsEl, live);\n\n function teardown() {\n cleanups.forEach((fn) => fn());\n cleanups.length = 0;\n root.replaceChildren();\n root.classList.remove('fg-root');\n root.removeAttribute('data-fg-mode');\n root.removeAttribute('data-fg-peek');\n }\n\n if (n === 0) {\n return { next() {}, prev() {}, goTo() {}, setMode() {}, setPeek() {}, getActiveIndex: () => -1, getMode: () => mode, destroy: teardown };\n }\n\n /* ── Normalized transform - identical function list for every mode ── */\n function setCardTransform(card, { x, y, z, rx, ry, s, zIndex, opacity, isActive }) {\n card.style.transform = `translate3d(${x}px,${y}px,${z}px) rotateX(${rx}deg) rotateY(${ry}deg) scale(${s})`;\n card.style.transformOrigin = 'center bottom';\n card.style.zIndex = String(zIndex);\n card.style.opacity = String(opacity);\n card.setAttribute('aria-selected', isActive ? 'true' : 'false');\n card.classList.toggle('is-active', isActive);\n }\n\n /* ── Build cards ── */\n const cardEls = items.map((item, i) => {\n const card = document.createElement('div');\n card.className = 'fg-card';\n card.tabIndex = i === active ? 0 : -1; // roving tabindex\n card.setAttribute('role', 'option');\n card.setAttribute('aria-label', item.label || `Item ${i + 1}`);\n if (item.color) {\n const c = folderColors(item.color);\n card.style.setProperty('--fg-folder-bg', item.color);\n card.style.setProperty('--fg-front', c.front);\n card.style.setProperty('--fg-front-solid', c.frontSolid);\n card.style.setProperty('--fg-label-on-color', c.label);\n }\n\n const svg = document.createElementNS(SVG_NS, 'svg');\n svg.setAttribute('class', 'fg-folder');\n svg.setAttribute('viewBox', '0 0 480 342');\n svg.setAttribute('preserveAspectRatio', 'none');\n svg.setAttribute('aria-hidden', 'true');\n const path = document.createElementNS(SVG_NS, 'path');\n path.setAttribute('d', opts.folderPath);\n path.setAttribute('fill', item.color ? folderColors(item.color).back : 'var(--fg-folder-bg)');\n svg.appendChild(path);\n\n card.appendChild(svg);\n\n renderContent(card, item, i);\n\n const front = document.createElement('div');\n front.className = 'fg-front';\n /* Decal: the photo prints on the folder's FRONT PANEL. The back and tab\n keep their color, so the folder stays a folder: colored tabs in the\n stack, a real material boundary at the front's lip. */\n if (item.decal) {\n card.classList.add('fg-card--decal');\n const photo = document.createElement('img');\n photo.className = 'fg-decal';\n photo.src = item.decal;\n photo.alt = '';\n photo.loading = 'lazy';\n photo.decoding = 'async';\n front.appendChild(photo);\n }\n if (item.label) {\n const label = document.createElement('div');\n label.className = 'fg-label';\n label.textContent = item.label;\n front.appendChild(label);\n }\n card.appendChild(front);\n\n scene.appendChild(card);\n return card;\n });\n\n /* ── Dots ── */\n const dots = cardEls.map((_, i) => {\n const dot = document.createElement('button');\n dot.type = 'button';\n dot.className = 'fg-dot';\n dot.setAttribute('role', 'tab');\n dot.setAttribute('aria-label', items[i].label || `Go to ${i + 1}`);\n on(dot, 'click', () => goTo(i));\n dotsEl.appendChild(dot);\n return dot;\n });\n\n /* ── Layouts ── */\n function layoutStack() {\n scene.style.height = '';\n cardEls.forEach((card, i) => {\n const behind = (i - active + n) % n;\n if (behind === 0) {\n setCardTransform(card, { x: 0, y: 100, z: 20, rx: 0, ry: 0, s: 1, zIndex: n + 1, opacity: 1, isActive: true });\n return;\n }\n const safeN = Math.max(n - 1, 1);\n const tz = -behind * 25 - behind * behind * 3;\n const rx = reduced ? 0 : -behind * 3;\n const ty = 100 - behind * 28 - behind * behind * 2;\n const sc = 1 - behind * 0.06 - (behind / safeN) * (behind / safeN) * 0.04;\n setCardTransform(card, { x: 0, y: ty, z: tz, rx, ry: 0, s: Math.max(sc, 0.5), zIndex: n - behind, opacity: 1 - behind * 0.08, isActive: false });\n });\n }\n function layoutGrid() {\n const sceneW = scene.clientWidth || MODE_WIDTHS.grid;\n // Two columns on narrow scenes so folders stay tappable and readable.\n const cols = sceneW < 520 ? 2 : n <= 4 ? 2 : 3;\n const gap = 16;\n const cardW = sceneW;\n const cardH = sceneW * (2 / 3);\n const sc = (sceneW - (cols - 1) * gap) / (cols * cardW);\n const scaledW = cardW * sc;\n const scaledH = cardH * sc;\n const tabScaled = 22 * sc;\n const rows = Math.ceil(n / cols);\n cardEls.forEach((card, i) => {\n const col = i % cols;\n const row = Math.floor(i / cols);\n const itemsInRow = Math.min(cols, n - row * cols);\n const rowW = itemsInRow * scaledW + (itemsInRow - 1) * gap;\n const rowStartX = (sceneW - rowW) / 2;\n const cellLeft = rowStartX + col * (scaledW + gap);\n const cellTop = row * (scaledH + tabScaled + gap) + tabScaled;\n const tx = cellLeft + scaledW / 2 - cardW / 2;\n const ty = cellTop + scaledH - cardH;\n setCardTransform(card, { x: tx, y: ty, z: 0, rx: 0, ry: 0, s: sc, zIndex: i === active ? 2 : 1, opacity: 1, isActive: i === active });\n });\n scene.style.height = rows * (scaledH + tabScaled + gap) + 20 + 'px';\n }\n function layoutCarousel() {\n const sceneW = scene.clientWidth || MODE_WIDTHS.carousel;\n const cardW = sceneW;\n const cardH = cardW * (2 / 3);\n const activeSc = 0.62;\n const activeW = cardW * activeSc;\n const sceneH = Math.max(cardH * activeSc + 40, 180);\n scene.style.height = sceneH + 'px';\n const offsetStep = Math.min(130, (sceneW - activeW) / 4);\n cardEls.forEach((card, i) => {\n let offset = i - active;\n if (n > 2 && offset > Math.floor(n / 2)) offset -= n;\n if (n > 2 && offset < -Math.floor(n / 2)) offset += n;\n const sc = Math.max(activeSc - Math.abs(offset) * 0.06, 0.28);\n const z = -Math.abs(offset) * 50;\n const op = Math.max(1 - Math.abs(offset) * 0.25, 0.15);\n const tx = sceneW / 2 + offset * offsetStep - cardW / 2;\n const ty = sceneH - 20 - cardH;\n setCardTransform(card, { x: tx, y: ty, z, rx: 0, ry: 0, s: sc, zIndex: n - Math.abs(offset), opacity: op, isActive: offset === 0 });\n });\n }\n // Scene width tracks the active mode so the layout math (which assumes the\n // mode's logical width) matches the actually-rendered card width. Layouts\n // then read scene.clientWidth, so everything stays self-consistent at any size.\n function sizeScene() {\n scene.style.width = `min(${MODE_WIDTHS[mode]}px, 92vw)`;\n }\n function applyLayout() {\n sizeScene();\n (mode === 'grid' ? layoutGrid : mode === 'carousel' ? layoutCarousel : layoutStack)();\n dots.forEach((d, i) => {\n d.classList.toggle('is-active', i === active);\n d.setAttribute('aria-selected', i === active ? 'true' : 'false');\n });\n cardEls.forEach((c, i) => { c.tabIndex = i === active ? 0 : -1; });\n const cur = items[active];\n live.textContent = cur && cur.label ? `${active + 1} of ${n}: ${cur.label}` : `${active + 1} of ${n}`;\n }\n\n /* ── Navigation ── */\n function goTo(i) {\n const next = opts.loop ? ((i % n) + n) % n : Math.min(Math.max(i, 0), n - 1);\n if (next === active) return;\n active = next;\n applyLayout();\n emit('fg-activechange', { index: active, item: items[active] });\n }\n function select(i) {\n emit('fg-select', { index: i, item: items[i] });\n if (typeof opts.onSelect === 'function') opts.onSelect(items[i], i);\n }\n function setMode(m) {\n if (m === mode || !MODE_WIDTHS[m]) return;\n mode = m;\n root.setAttribute('data-fg-mode', mode);\n applyLayout();\n emit('fg-modechange', { mode });\n }\n function setPeek(p) {\n if (!PEEK_MODES.has(p)) return;\n root.setAttribute('data-fg-peek', p);\n }\n\n /* ── Interaction ── */\n cardEls.forEach((card, i) => {\n on(card, 'click', () => { i === active ? select(i) : goTo(i); });\n on(card, 'keydown', (e) => {\n if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); select(i); }\n });\n });\n /* Arrow keys work two ways, matching the reference implementation:\n 1. Focus already inside the gallery (Tab to a card/dot) - bubbles here\n and re-focuses the new active card, standard roving-tabindex UX.\n 2. Mouse hovering the gallery, focus anywhere else on the page - a\n document-level listener picks it up. Without this route, arrow\n keys only work AFTER a click puts focus on a card - and a click is\n also what fires onSelect, which in real use is often a link. Making\n keyboard nav depend on that first click means the only way to\n \"activate\" arrows is to risk navigating away. Hover is enough. */\n function handleArrowKeys(e) {\n if (e.key === 'ArrowRight' || e.key === 'ArrowDown') { e.preventDefault(); goTo(active + 1); return true; }\n if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') { e.preventDefault(); goTo(active - 1); return true; }\n if (e.key === 'Home') { e.preventDefault(); goTo(0); return true; }\n if (e.key === 'End') { e.preventDefault(); goTo(n - 1); return true; }\n return false;\n }\n on(scene, 'keydown', (e) => { if (handleArrowKeys(e)) cardEls[active].focus(); });\n on(dotsEl, 'keydown', (e) => { if (handleArrowKeys(e)) cardEls[active].focus(); });\n\n let hovered = false;\n on(root, 'mouseenter', () => { hovered = true; });\n on(root, 'mouseleave', () => { hovered = false; });\n on(document, 'keydown', (e) => {\n if (!hovered) return;\n // Don't hijack arrows if focus is on an interactive control outside\n // the gallery (nav link, a form field elsewhere on the page).\n const ae = document.activeElement;\n if (ae && ae !== document.body && !root.contains(ae)) return;\n handleArrowKeys(e); // no forced focus - the user hasn't tabbed in\n });\n\n if (opts.scrollNav) {\n /* Bound to root, not just the scene box: root is the widget's full\n footprint (scene + dots), so wheel events over the dots row or any\n gap the consumer's own layout leaves around the gallery still cycle\n it, instead of requiring the pointer over the exact folder art. */\n on(root, 'wheel', (e) => {\n if (mode === 'grid') return;\n const atStart = !opts.loop && active === 0 && e.deltaY < 0;\n const atEnd = !opts.loop && active === n - 1 && e.deltaY > 0;\n if (atStart || atEnd) return;\n e.preventDefault();\n if (scrollCooldown) return;\n scrollAccum += e.deltaY;\n if (Math.abs(scrollAccum) >= SCROLL_THRESHOLD) {\n const dir = scrollAccum > 0 ? 1 : -1;\n scrollAccum = 0;\n scrollCooldown = true;\n goTo(active + dir);\n setTimeout(() => { scrollCooldown = false; scrollAccum = 0; }, SCROLL_COOLDOWN[mode] || 300);\n }\n }, { passive: false });\n }\n\n /* Dominant-axis swipe: up or left advances, down or right goes back.\n Horizontal is the natural phone gesture in carousel; vertical matches\n the wheel direction on the stack. Both work everywhere. */\n let touchStartX = 0;\n let touchStartY = 0;\n on(scene, 'touchstart', (e) => {\n touchStartX = e.touches[0].clientX;\n touchStartY = e.touches[0].clientY;\n }, { passive: true });\n on(scene, 'touchend', (e) => {\n const dx = touchStartX - e.changedTouches[0].clientX;\n const dy = touchStartY - e.changedTouches[0].clientY;\n const ax = Math.abs(dx);\n const ay = Math.abs(dy);\n if (Math.max(ax, ay) < 40) return;\n const dir = ax > ay ? (dx > 0 ? 1 : -1) : (dy > 0 ? 1 : -1);\n goTo(active + dir);\n }, { passive: true });\n\n let resizeTimer;\n on(window, 'resize', () => { clearTimeout(resizeTimer); resizeTimer = setTimeout(applyLayout, 150); });\n\n applyLayout();\n\n /* ── Public handle ── */\n return {\n next: () => goTo(active + 1),\n prev: () => goTo(active - 1),\n goTo,\n setMode,\n setPeek,\n getActiveIndex: () => active,\n getMode: () => mode,\n destroy: teardown,\n };\n}\n\nexport default createFolderGallery;\n"],"mappings":"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","names":[]}
|