@vp-tw/dirwell 0.1.0-alpha.3 → 0.1.0-alpha.5
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/CHANGELOG.md +12 -0
- package/THEMING.md +7 -0
- package/dist/bin.mjs +1 -1
- package/dist/crosswave-runtime.js +590 -0
- package/dist/index.d.mts +13 -1
- package/dist/index.mjs +183 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @vp-tw/dirwell
|
|
2
2
|
|
|
3
|
+
## 0.1.0-alpha.5
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Handle rejected cross-document transition readiness when the browser skips or interrupts an animation, retaining native navigation without an unhandled Promise rejection.
|
|
8
|
+
|
|
9
|
+
## 0.1.0-alpha.4
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- Add the opt-in Crosswave theme with a PSP-inspired category rail, animated light ribbons, directory transitions, responsive file navigation, keyboard controls, and standard gamepad support. Include a live media-library example and document configuration and fallback behavior.
|
|
14
|
+
|
|
3
15
|
## 0.1.0-alpha.3
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/THEMING.md
CHANGED
|
@@ -13,6 +13,13 @@ The [theme guide](https://vp-tw.github.io/dirwell/themes/) lists every default-t
|
|
|
13
13
|
option, its accepted input, default, result, and use case. This file describes
|
|
14
14
|
the component contract for theme authors.
|
|
15
15
|
|
|
16
|
+
## Crosswave renderer
|
|
17
|
+
|
|
18
|
+
`createCrosswaveTheme()` provides an independent, PSP-inspired renderer with
|
|
19
|
+
category navigation, local search, ribbon light, and optional controller input.
|
|
20
|
+
Its styles, assets, and runtime are separate from Ledger. See [Crosswave](https://vp-tw.github.io/dirwell/themes/#crosswave)
|
|
21
|
+
for options, keyboard behavior, browser transition support, and hardware-test limits.
|
|
22
|
+
|
|
16
23
|
## Component layers
|
|
17
24
|
|
|
18
25
|
Use an `ExplorerTheme` to replace the complete renderer. Use component layers
|
package/dist/bin.mjs
CHANGED
|
@@ -0,0 +1,590 @@
|
|
|
1
|
+
(() => {
|
|
2
|
+
"use strict";
|
|
3
|
+
const root = document.documentElement;
|
|
4
|
+
const reduced = matchMedia("(prefers-reduced-motion: reduce)");
|
|
5
|
+
// Registered before first paint for cross-document navigation and BFCache restores.
|
|
6
|
+
for (const name of ["pageswap", "pagereveal"]) {
|
|
7
|
+
addEventListener(name, (event) => {
|
|
8
|
+
const transition = event.viewTransition;
|
|
9
|
+
if (!transition) return;
|
|
10
|
+
// A skipped or interrupted animation must not reject normal file navigation.
|
|
11
|
+
transition.ready.catch(() => {});
|
|
12
|
+
if (reduced.matches || root.dataset.cwTransitions === "false") transition.skipTransition();
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
document.addEventListener("DOMContentLoaded", () => {
|
|
16
|
+
const main = document.querySelector("[data-cw-root]");
|
|
17
|
+
if (!main) return;
|
|
18
|
+
document.body.dataset.cwEnhanced = "true";
|
|
19
|
+
const key = `dirwell:crosswave:${new URL(main.dataset.cwRoot, document.baseURI).pathname}`;
|
|
20
|
+
const read = (name) => {
|
|
21
|
+
try {
|
|
22
|
+
return localStorage.getItem(`${key}:${name}`);
|
|
23
|
+
} catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
const write = (name, value) => {
|
|
28
|
+
try {
|
|
29
|
+
localStorage.setItem(`${key}:${name}`, value);
|
|
30
|
+
} catch {}
|
|
31
|
+
};
|
|
32
|
+
const colors = {
|
|
33
|
+
azure: [0.17, 0.31, 0.52],
|
|
34
|
+
violet: [0.36, 0.25, 0.49],
|
|
35
|
+
amber: [0.44, 0.3, 0.1],
|
|
36
|
+
rose: [0.44, 0.25, 0.33],
|
|
37
|
+
jade: [0.13, 0.37, 0.32],
|
|
38
|
+
graphite: [0.26, 0.3, 0.38],
|
|
39
|
+
};
|
|
40
|
+
const savedColor = read("color");
|
|
41
|
+
if (Object.hasOwn(colors, savedColor)) root.dataset.cwColor = savedColor;
|
|
42
|
+
let paused = root.dataset.cwMotion === "false" || read("paused") === "true";
|
|
43
|
+
const rows = [...document.querySelectorAll("[data-cw-entry]")];
|
|
44
|
+
const tabs = [...document.querySelectorAll("[data-cw-category]")];
|
|
45
|
+
const search = document.querySelector("#cw-search");
|
|
46
|
+
const clear = document.querySelector("[data-cw-clear]");
|
|
47
|
+
const empty = document.querySelector("[data-cw-empty]");
|
|
48
|
+
const status = document.querySelector("[data-cw-status]");
|
|
49
|
+
const detail = document.querySelector(".cw-detail");
|
|
50
|
+
const icons = new Map(tabs.map((tab) => [tab.dataset.cwCategory, tab.querySelector("svg")]));
|
|
51
|
+
const linkIcon = rows.find((row) => row.dataset.icon === "link")?.querySelector("svg");
|
|
52
|
+
if (linkIcon) icons.set("link", linkIcon);
|
|
53
|
+
let category = "all",
|
|
54
|
+
selected = null,
|
|
55
|
+
composing = false,
|
|
56
|
+
searchTimer;
|
|
57
|
+
const visible = () => rows.filter((row) => !row.hidden);
|
|
58
|
+
const select = (row, focus = false) => {
|
|
59
|
+
if (selected) selected.classList.remove("cw-selected");
|
|
60
|
+
selected = row;
|
|
61
|
+
if (!row) {
|
|
62
|
+
detail.hidden = true;
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
row.classList.add("cw-selected");
|
|
66
|
+
const values = {
|
|
67
|
+
name: "name",
|
|
68
|
+
path: "path",
|
|
69
|
+
size: "size",
|
|
70
|
+
modified: "modified",
|
|
71
|
+
status: "status",
|
|
72
|
+
};
|
|
73
|
+
for (const [field, data] of Object.entries(values))
|
|
74
|
+
document.querySelector(`[data-cw-detail-${field}]`).textContent = row.dataset[data];
|
|
75
|
+
const host = document.querySelector("[data-cw-detail-icon]");
|
|
76
|
+
host.replaceChildren();
|
|
77
|
+
const icon = icons.get(row.dataset.icon);
|
|
78
|
+
if (icon) host.append(icon.cloneNode(true));
|
|
79
|
+
const link = row.querySelector("a");
|
|
80
|
+
const open = document.querySelector("[data-cw-open]");
|
|
81
|
+
open.hidden = !link;
|
|
82
|
+
if (link) {
|
|
83
|
+
open.href = link.href;
|
|
84
|
+
open.textContent = row.dataset.status.includes("declared target")
|
|
85
|
+
? "Show declared target"
|
|
86
|
+
: row.dataset.category === "folder"
|
|
87
|
+
? "Open folder"
|
|
88
|
+
: "Open file";
|
|
89
|
+
if (row.dataset.exits === "true") {
|
|
90
|
+
open.target = "_blank";
|
|
91
|
+
open.rel = "noopener";
|
|
92
|
+
open.removeAttribute("data-cw-navigation");
|
|
93
|
+
} else {
|
|
94
|
+
open.removeAttribute("target");
|
|
95
|
+
open.removeAttribute("rel");
|
|
96
|
+
open.setAttribute("data-cw-navigation", "");
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
detail.hidden = false;
|
|
100
|
+
if (focus) {
|
|
101
|
+
link?.focus({ preventScroll: true });
|
|
102
|
+
row.scrollIntoView({ block: "nearest", behavior: reduced.matches ? "instant" : "smooth" });
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
const applyFilter = () => {
|
|
106
|
+
const query = search.value.trim().toLocaleLowerCase();
|
|
107
|
+
let count = 0;
|
|
108
|
+
for (const row of rows) {
|
|
109
|
+
row.hidden =
|
|
110
|
+
(category !== "all" && row.dataset.category !== category) ||
|
|
111
|
+
!`${row.dataset.name} ${row.dataset.path}`.toLocaleLowerCase().includes(query);
|
|
112
|
+
if (!row.hidden) count++;
|
|
113
|
+
}
|
|
114
|
+
empty.hidden = count !== 0;
|
|
115
|
+
empty.textContent = !rows.length
|
|
116
|
+
? "This folder is empty."
|
|
117
|
+
: query
|
|
118
|
+
? `No files match “${search.value.trim()}”.`
|
|
119
|
+
: "No files in this category.";
|
|
120
|
+
document.querySelector("[data-cw-count]").textContent =
|
|
121
|
+
`${count} ${count === 1 ? "item" : "items"}`;
|
|
122
|
+
status.textContent = `${count} ${count === 1 ? "item" : "items"} shown`;
|
|
123
|
+
clear.hidden = !search.value;
|
|
124
|
+
if (!selected || selected.hidden) select(visible()[0] ?? null);
|
|
125
|
+
};
|
|
126
|
+
const setCategory = (next, focus = false) => {
|
|
127
|
+
category = next;
|
|
128
|
+
for (const tab of tabs) {
|
|
129
|
+
const active = tab.dataset.cwCategory === next;
|
|
130
|
+
tab.classList.toggle("cw-active", active);
|
|
131
|
+
tab.setAttribute("aria-selected", String(active));
|
|
132
|
+
tab.tabIndex = active ? 0 : -1;
|
|
133
|
+
if (active) {
|
|
134
|
+
document.querySelector("[data-cw-heading]").textContent =
|
|
135
|
+
tab.querySelector("span").textContent;
|
|
136
|
+
document.querySelector("#cw-panel").setAttribute("role", "tabpanel");
|
|
137
|
+
document.querySelector("#cw-panel").setAttribute("aria-labelledby", tab.id);
|
|
138
|
+
if (focus) tab.focus({ preventScroll: true });
|
|
139
|
+
const rail = document.querySelector(".cw-rail");
|
|
140
|
+
rail.scrollTo({
|
|
141
|
+
left: Math.max(0, tab.offsetLeft - tabs[0].offsetLeft),
|
|
142
|
+
behavior: reduced.matches ? "instant" : "smooth",
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
selected = null;
|
|
147
|
+
rows.forEach((row) => row.classList.remove("cw-selected"));
|
|
148
|
+
applyFilter();
|
|
149
|
+
main.classList.remove("cw-category-changing");
|
|
150
|
+
void main.offsetWidth;
|
|
151
|
+
main.classList.add("cw-category-changing");
|
|
152
|
+
};
|
|
153
|
+
const moveCategory = (direction) => {
|
|
154
|
+
const index = tabs.findIndex((tab) => tab.dataset.cwCategory === category);
|
|
155
|
+
setCategory(tabs[(index + direction + tabs.length) % tabs.length].dataset.cwCategory, true);
|
|
156
|
+
};
|
|
157
|
+
const moveRow = (direction) => {
|
|
158
|
+
const list = visible().filter((row) => row.querySelector("a"));
|
|
159
|
+
if (!list.length) return;
|
|
160
|
+
let index = list.indexOf(selected);
|
|
161
|
+
index = index < 0 ? 0 : Math.max(0, Math.min(list.length - 1, index + direction));
|
|
162
|
+
select(list[index], true);
|
|
163
|
+
};
|
|
164
|
+
for (const tab of tabs) {
|
|
165
|
+
tab.addEventListener("click", () => setCategory(tab.dataset.cwCategory));
|
|
166
|
+
const count =
|
|
167
|
+
tab.dataset.cwCategory === "all"
|
|
168
|
+
? rows.length
|
|
169
|
+
: rows.filter((row) => row.dataset.category === tab.dataset.cwCategory).length;
|
|
170
|
+
tab.querySelector("small").textContent = String(count);
|
|
171
|
+
}
|
|
172
|
+
for (const row of rows) {
|
|
173
|
+
row.addEventListener("pointerenter", (event) => {
|
|
174
|
+
if (event.pointerType !== "touch") select(row);
|
|
175
|
+
});
|
|
176
|
+
row.addEventListener("focusin", () => select(row));
|
|
177
|
+
}
|
|
178
|
+
search.addEventListener("compositionstart", () => {
|
|
179
|
+
composing = true;
|
|
180
|
+
clearTimeout(searchTimer);
|
|
181
|
+
});
|
|
182
|
+
search.addEventListener("compositionend", () => {
|
|
183
|
+
composing = false;
|
|
184
|
+
applyFilter();
|
|
185
|
+
});
|
|
186
|
+
search.addEventListener("input", () => {
|
|
187
|
+
if (!composing) {
|
|
188
|
+
clearTimeout(searchTimer);
|
|
189
|
+
searchTimer = setTimeout(applyFilter, 80);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
clear.addEventListener("click", () => {
|
|
193
|
+
search.value = "";
|
|
194
|
+
applyFilter();
|
|
195
|
+
search.focus();
|
|
196
|
+
});
|
|
197
|
+
const activate = () => {
|
|
198
|
+
const link = selected?.querySelector("a");
|
|
199
|
+
if (!link) return;
|
|
200
|
+
if (link.target !== "_blank") {
|
|
201
|
+
link.click();
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
// Controller polling does not necessarily grant a browser user gesture.
|
|
205
|
+
// Detach the blank tab before loading source content, preserving opener isolation.
|
|
206
|
+
const tab = window.open("about:blank", "_blank");
|
|
207
|
+
if (tab) {
|
|
208
|
+
tab.opener = null;
|
|
209
|
+
tab.location.replace(link.href);
|
|
210
|
+
} else {
|
|
211
|
+
link.focus({ preventScroll: true });
|
|
212
|
+
status.classList.remove("cw-sr");
|
|
213
|
+
status.classList.add("cw-notice");
|
|
214
|
+
status.textContent =
|
|
215
|
+
"Your browser blocked the new tab. Press Enter or click the file to open it.";
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
const back = () => document.querySelector("[data-cw-parent]")?.click();
|
|
219
|
+
const editing = () =>
|
|
220
|
+
document.activeElement?.matches("input,textarea,select,[contenteditable=true]");
|
|
221
|
+
document.addEventListener("keydown", (event) => {
|
|
222
|
+
if (
|
|
223
|
+
event.isComposing ||
|
|
224
|
+
event.keyCode === 229 ||
|
|
225
|
+
composing ||
|
|
226
|
+
event.altKey ||
|
|
227
|
+
event.ctrlKey ||
|
|
228
|
+
event.metaKey
|
|
229
|
+
)
|
|
230
|
+
return;
|
|
231
|
+
if (editing()) {
|
|
232
|
+
if (event.key === "Escape" && document.activeElement === search) {
|
|
233
|
+
search.value = "";
|
|
234
|
+
applyFilter();
|
|
235
|
+
search.blur();
|
|
236
|
+
selected?.querySelector("a")?.focus();
|
|
237
|
+
}
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
if (event.key === "ArrowLeft" || event.key === "ArrowRight") {
|
|
241
|
+
event.preventDefault();
|
|
242
|
+
moveCategory(event.key === "ArrowLeft" ? -1 : 1);
|
|
243
|
+
} else if (event.key === "ArrowUp" || event.key === "ArrowDown") {
|
|
244
|
+
event.preventDefault();
|
|
245
|
+
moveRow(event.key === "ArrowUp" ? -1 : 1);
|
|
246
|
+
} else if (event.key === "Home" || event.key === "End") {
|
|
247
|
+
event.preventDefault();
|
|
248
|
+
if (document.activeElement?.matches("[role=tab]")) {
|
|
249
|
+
setCategory((event.key === "Home" ? tabs[0] : tabs.at(-1)).dataset.cwCategory, true);
|
|
250
|
+
} else {
|
|
251
|
+
const list = visible().filter((row) => row.querySelector("a"));
|
|
252
|
+
select(event.key === "Home" ? list[0] : list.at(-1), true);
|
|
253
|
+
}
|
|
254
|
+
} else if (event.key === "Backspace") {
|
|
255
|
+
event.preventDefault();
|
|
256
|
+
back();
|
|
257
|
+
} else if (event.key === "/") {
|
|
258
|
+
event.preventDefault();
|
|
259
|
+
search.focus();
|
|
260
|
+
} else if (event.key === "Enter" && document.activeElement?.matches("[role=tab]")) {
|
|
261
|
+
event.preventDefault();
|
|
262
|
+
selected?.querySelector("a")?.focus();
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
for (const selector of [
|
|
266
|
+
".cw-rail",
|
|
267
|
+
".cw-search",
|
|
268
|
+
".cw-clock",
|
|
269
|
+
".cw-key-help",
|
|
270
|
+
".cw-appearance",
|
|
271
|
+
])
|
|
272
|
+
document.querySelector(selector).hidden = false;
|
|
273
|
+
const clock = document.querySelector(".cw-clock");
|
|
274
|
+
const updateClock = () => {
|
|
275
|
+
const now = new Date();
|
|
276
|
+
clock.dateTime = now.toISOString();
|
|
277
|
+
clock.textContent = new Intl.DateTimeFormat(undefined, {
|
|
278
|
+
month: "numeric",
|
|
279
|
+
day: "numeric",
|
|
280
|
+
hour: "2-digit",
|
|
281
|
+
minute: "2-digit",
|
|
282
|
+
}).format(now);
|
|
283
|
+
};
|
|
284
|
+
updateClock();
|
|
285
|
+
let clockTimer = setInterval(updateClock, 30000);
|
|
286
|
+
const colorSelect = document.querySelector("#cw-color");
|
|
287
|
+
colorSelect.value = root.dataset.cwColor;
|
|
288
|
+
colorSelect.addEventListener("change", () => {
|
|
289
|
+
root.dataset.cwColor = colorSelect.value;
|
|
290
|
+
write("color", colorSelect.value);
|
|
291
|
+
wave?.draw(0);
|
|
292
|
+
});
|
|
293
|
+
const motionButton = document.querySelector("button[data-cw-motion]");
|
|
294
|
+
const updateMotion = () => {
|
|
295
|
+
motionButton.setAttribute("aria-pressed", String(paused));
|
|
296
|
+
motionButton.querySelector("span").textContent = reduced.matches
|
|
297
|
+
? "Reduced motion"
|
|
298
|
+
: paused
|
|
299
|
+
? "Resume waves"
|
|
300
|
+
: "Pause waves";
|
|
301
|
+
motionButton.disabled =
|
|
302
|
+
reduced.matches || root.dataset.cwMotion === "false" || !wave?.available;
|
|
303
|
+
if (!wave?.available) motionButton.querySelector("span").textContent = "Static background";
|
|
304
|
+
wave?.sync();
|
|
305
|
+
};
|
|
306
|
+
motionButton.addEventListener("click", () => {
|
|
307
|
+
paused = !paused;
|
|
308
|
+
write("paused", String(paused));
|
|
309
|
+
updateMotion();
|
|
310
|
+
});
|
|
311
|
+
let wave = createWave(
|
|
312
|
+
document.querySelector("#cw-wave"),
|
|
313
|
+
colors,
|
|
314
|
+
root,
|
|
315
|
+
() => !paused && !reduced.matches && !document.hidden,
|
|
316
|
+
);
|
|
317
|
+
document.querySelector("#cw-wave").addEventListener("cw-wave-fallback", updateMotion);
|
|
318
|
+
reduced.addEventListener("change", updateMotion);
|
|
319
|
+
updateMotion();
|
|
320
|
+
setCategory("all");
|
|
321
|
+
const scroll = document.querySelector(".cw-list-scroll");
|
|
322
|
+
const fitList = () => {
|
|
323
|
+
const footer = document.querySelector(".cw-bottom");
|
|
324
|
+
const available = Math.max(
|
|
325
|
+
80,
|
|
326
|
+
innerHeight -
|
|
327
|
+
scroll.getBoundingClientRect().top -
|
|
328
|
+
footer.getBoundingClientRect().height -
|
|
329
|
+
18,
|
|
330
|
+
);
|
|
331
|
+
scroll.style.maxHeight = `${Math.floor(available)}px`;
|
|
332
|
+
scroll.style.minHeight = `${Math.min(160, Math.floor(available))}px`;
|
|
333
|
+
};
|
|
334
|
+
const layoutObserver = new ResizeObserver(fitList);
|
|
335
|
+
for (const element of [
|
|
336
|
+
document.querySelector(".cw-top"),
|
|
337
|
+
document.querySelector(".cw-list-head"),
|
|
338
|
+
document.querySelector(".cw-rail"),
|
|
339
|
+
document.querySelector(".cw-bottom"),
|
|
340
|
+
])
|
|
341
|
+
layoutObserver.observe(element);
|
|
342
|
+
addEventListener("resize", fitList);
|
|
343
|
+
fitList();
|
|
344
|
+
|
|
345
|
+
// VueUse's useGamepad demonstrates event-driven connection and rAF polling.
|
|
346
|
+
// Read fresh standard-mapping snapshots; controls here are original and framework-free.
|
|
347
|
+
let padFrame = 0,
|
|
348
|
+
padHeld = new Map(),
|
|
349
|
+
stopped = false,
|
|
350
|
+
padIdentity = null;
|
|
351
|
+
const indicator = document.querySelector(".cw-controller");
|
|
352
|
+
const pollPad = (time) => {
|
|
353
|
+
if (stopped || document.hidden) return;
|
|
354
|
+
let pad;
|
|
355
|
+
try {
|
|
356
|
+
pad = Array.from(navigator.getGamepads?.() ?? []).find(
|
|
357
|
+
(p) => p?.connected && p.mapping === "standard",
|
|
358
|
+
);
|
|
359
|
+
} catch {
|
|
360
|
+
pad = null;
|
|
361
|
+
}
|
|
362
|
+
indicator.hidden = !pad;
|
|
363
|
+
if (!pad) {
|
|
364
|
+
padHeld.clear();
|
|
365
|
+
padIdentity = null;
|
|
366
|
+
padFrame = 0;
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
const controllerLabel = document.querySelector("[data-cw-controller-label]");
|
|
370
|
+
if (controllerLabel.textContent !== "Controller · Confirm / Back")
|
|
371
|
+
controllerLabel.textContent = "Controller · Confirm / Back";
|
|
372
|
+
const pressed = (i) => Boolean(pad.buttons[i]?.pressed || pad.buttons[i]?.value > 0.5);
|
|
373
|
+
const active = {
|
|
374
|
+
left: pressed(14) || (pad.axes[0] ?? 0) < -0.4,
|
|
375
|
+
right: pressed(15) || (pad.axes[0] ?? 0) > 0.4,
|
|
376
|
+
up: pressed(12) || (pad.axes[1] ?? 0) < -0.4,
|
|
377
|
+
down: pressed(13) || (pad.axes[1] ?? 0) > 0.4,
|
|
378
|
+
confirm: pressed(0),
|
|
379
|
+
back: pressed(1),
|
|
380
|
+
search: pressed(9),
|
|
381
|
+
};
|
|
382
|
+
const identity = `${pad.index}:${pad.id}`;
|
|
383
|
+
if (padIdentity !== identity) {
|
|
384
|
+
padIdentity = identity;
|
|
385
|
+
padHeld.clear();
|
|
386
|
+
// A held confirm must not open another item after document navigation.
|
|
387
|
+
for (const [action, on] of Object.entries(active))
|
|
388
|
+
if (on) padHeld.set(action, { next: Infinity });
|
|
389
|
+
}
|
|
390
|
+
for (const [action, on] of Object.entries(active)) {
|
|
391
|
+
if (!on) {
|
|
392
|
+
padHeld.delete(action);
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
const hold = padHeld.get(action);
|
|
396
|
+
const repeat = ["left", "right", "up", "down"].includes(action);
|
|
397
|
+
if (hold && (!repeat || time < hold.next)) continue;
|
|
398
|
+
padHeld.set(action, { next: time + (hold ? 140 : 420) });
|
|
399
|
+
if (!document.hasFocus() || composing) continue;
|
|
400
|
+
if (editing()) {
|
|
401
|
+
if (action === "back") {
|
|
402
|
+
if (document.activeElement === search) {
|
|
403
|
+
search.value = "";
|
|
404
|
+
applyFilter();
|
|
405
|
+
}
|
|
406
|
+
document.activeElement?.blur();
|
|
407
|
+
selected?.querySelector("a")?.focus();
|
|
408
|
+
}
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
if (action === "left" || action === "right") moveCategory(action === "left" ? -1 : 1);
|
|
412
|
+
else if (action === "up" || action === "down") moveRow(action === "up" ? -1 : 1);
|
|
413
|
+
else if (action === "confirm") activate();
|
|
414
|
+
else if (action === "back") back();
|
|
415
|
+
else if (action === "search") search.focus();
|
|
416
|
+
}
|
|
417
|
+
padFrame = requestAnimationFrame(pollPad);
|
|
418
|
+
};
|
|
419
|
+
const startPad = () => {
|
|
420
|
+
if (root.dataset.cwGamepad !== "false" && !document.hidden && !padFrame) {
|
|
421
|
+
stopped = false;
|
|
422
|
+
padFrame = requestAnimationFrame(pollPad);
|
|
423
|
+
}
|
|
424
|
+
};
|
|
425
|
+
addEventListener("gamepadconnected", startPad);
|
|
426
|
+
addEventListener("gamepaddisconnected", () => {
|
|
427
|
+
padHeld.clear();
|
|
428
|
+
indicator.hidden = true;
|
|
429
|
+
startPad();
|
|
430
|
+
});
|
|
431
|
+
startPad();
|
|
432
|
+
document.addEventListener("visibilitychange", () => {
|
|
433
|
+
wave?.sync();
|
|
434
|
+
if (document.hidden) {
|
|
435
|
+
cancelAnimationFrame(padFrame);
|
|
436
|
+
padFrame = 0;
|
|
437
|
+
padHeld.clear();
|
|
438
|
+
padIdentity = null;
|
|
439
|
+
clearInterval(clockTimer);
|
|
440
|
+
} else {
|
|
441
|
+
clearInterval(clockTimer);
|
|
442
|
+
updateClock();
|
|
443
|
+
clockTimer = setInterval(updateClock, 30000);
|
|
444
|
+
startPad();
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
addEventListener("pagehide", () => {
|
|
448
|
+
stopped = true;
|
|
449
|
+
cancelAnimationFrame(padFrame);
|
|
450
|
+
padFrame = 0;
|
|
451
|
+
padHeld.clear();
|
|
452
|
+
padIdentity = null;
|
|
453
|
+
clearInterval(clockTimer);
|
|
454
|
+
wave?.stop();
|
|
455
|
+
});
|
|
456
|
+
addEventListener("pageshow", (event) => {
|
|
457
|
+
if (event.persisted) {
|
|
458
|
+
stopped = false;
|
|
459
|
+
clearInterval(clockTimer);
|
|
460
|
+
updateClock();
|
|
461
|
+
clockTimer = setInterval(updateClock, 30000);
|
|
462
|
+
wave?.sync();
|
|
463
|
+
startPad();
|
|
464
|
+
}
|
|
465
|
+
});
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
function createWave(canvas, colors, root, moving) {
|
|
469
|
+
let gl;
|
|
470
|
+
try {
|
|
471
|
+
gl = canvas.getContext("webgl", {
|
|
472
|
+
alpha: false,
|
|
473
|
+
antialias: false,
|
|
474
|
+
powerPreference: "low-power",
|
|
475
|
+
});
|
|
476
|
+
} catch {
|
|
477
|
+
return null;
|
|
478
|
+
}
|
|
479
|
+
if (!gl) return null;
|
|
480
|
+
const vertex = "attribute vec2 p;void main(){gl_Position=vec4(p,0.,1.);}";
|
|
481
|
+
const fragment = `precision highp float;uniform vec2 resolution;uniform float time;uniform vec3 tint;
|
|
482
|
+
void main(){vec2 uv=gl_FragCoord.xy/resolution;float x=uv.x;float y=uv.y;float t=time;
|
|
483
|
+
vec3 ground=mix(tint*.22,tint,clamp(y*.85+x*.1,0.,1.));ground+=vec3(.035)*pow(max(0.,1.-distance(uv,vec2(.2,.9))),3.);
|
|
484
|
+
float light=0.;for(int i=0;i<3;i++){float k=float(i);float center=.40+.07*sin(x*4.2+t*.17+k*.8)+.035*cos(x*7.1-t*.11+k*1.4)+k*.024;
|
|
485
|
+
float breadth=.012+.037*(.5+.5*sin(x*2.7+t*.09+k));float d=y-center;float sheet=exp(-pow(d/breadth,2.));float edge=exp(-abs(d-breadth*.65)*430.);
|
|
486
|
+
float fold=exp(-abs(d+breadth*.35)*170.);float taper=pow(sin(clamp(x,0.,1.)*3.14159),.6);light+=(sheet*.07+edge*.23+fold*.09)*taper;}
|
|
487
|
+
ground+=mix(vec3(.6,.8,1.),vec3(1.),.75)*min(light,.12);gl_FragColor=vec4(ground,1.);}`;
|
|
488
|
+
const shader = (type, source) => {
|
|
489
|
+
const s = gl.createShader(type);
|
|
490
|
+
if (!s) return null;
|
|
491
|
+
gl.shaderSource(s, source);
|
|
492
|
+
gl.compileShader(s);
|
|
493
|
+
if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) {
|
|
494
|
+
gl.deleteShader(s);
|
|
495
|
+
return null;
|
|
496
|
+
}
|
|
497
|
+
return s;
|
|
498
|
+
};
|
|
499
|
+
const vs = shader(gl.VERTEX_SHADER, vertex),
|
|
500
|
+
fs = shader(gl.FRAGMENT_SHADER, fragment);
|
|
501
|
+
if (!vs || !fs) {
|
|
502
|
+
if (vs) gl.deleteShader(vs);
|
|
503
|
+
if (fs) gl.deleteShader(fs);
|
|
504
|
+
return null;
|
|
505
|
+
}
|
|
506
|
+
const program = gl.createProgram();
|
|
507
|
+
if (!program) return null;
|
|
508
|
+
gl.attachShader(program, vs);
|
|
509
|
+
gl.attachShader(program, fs);
|
|
510
|
+
gl.linkProgram(program);
|
|
511
|
+
gl.deleteShader(vs);
|
|
512
|
+
gl.deleteShader(fs);
|
|
513
|
+
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
514
|
+
gl.deleteProgram(program);
|
|
515
|
+
return null;
|
|
516
|
+
}
|
|
517
|
+
const buffer = gl.createBuffer();
|
|
518
|
+
if (!buffer) {
|
|
519
|
+
gl.deleteProgram(program);
|
|
520
|
+
return null;
|
|
521
|
+
}
|
|
522
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
|
523
|
+
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]), gl.STATIC_DRAW);
|
|
524
|
+
gl.useProgram(program);
|
|
525
|
+
const p = gl.getAttribLocation(program, "p");
|
|
526
|
+
gl.enableVertexAttribArray(p);
|
|
527
|
+
gl.vertexAttribPointer(p, 2, gl.FLOAT, false, 0, 0);
|
|
528
|
+
const r = gl.getUniformLocation(program, "resolution"),
|
|
529
|
+
t = gl.getUniformLocation(program, "time"),
|
|
530
|
+
c = gl.getUniformLocation(program, "tint");
|
|
531
|
+
let frame = 0,
|
|
532
|
+
last = 0,
|
|
533
|
+
lost = false,
|
|
534
|
+
frozenTime = 17;
|
|
535
|
+
const draw = (now) => {
|
|
536
|
+
if (lost) return;
|
|
537
|
+
const ratio = Math.min(devicePixelRatio || 1, 1.5, 1600 / innerWidth, 1000 / innerHeight),
|
|
538
|
+
width = Math.round(innerWidth * ratio),
|
|
539
|
+
height = Math.round(innerHeight * ratio);
|
|
540
|
+
if (canvas.width !== width || canvas.height !== height) {
|
|
541
|
+
canvas.width = width;
|
|
542
|
+
canvas.height = height;
|
|
543
|
+
gl.viewport(0, 0, width, height);
|
|
544
|
+
}
|
|
545
|
+
if (moving()) frozenTime = (Date.now() % 86400000) / 1000;
|
|
546
|
+
gl.uniform2f(r, width, height);
|
|
547
|
+
gl.uniform1f(t, frozenTime);
|
|
548
|
+
gl.uniform3fv(c, colors[root.dataset.cwColor] ?? colors.azure);
|
|
549
|
+
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
|
550
|
+
canvas.parentElement.dataset.ready = "true";
|
|
551
|
+
last = now;
|
|
552
|
+
};
|
|
553
|
+
const tick = (now) => {
|
|
554
|
+
frame = 0;
|
|
555
|
+
if (!moving() || lost) return;
|
|
556
|
+
if (now - last >= 33) draw(now);
|
|
557
|
+
frame = requestAnimationFrame(tick);
|
|
558
|
+
};
|
|
559
|
+
const stop = () => {
|
|
560
|
+
cancelAnimationFrame(frame);
|
|
561
|
+
frame = 0;
|
|
562
|
+
};
|
|
563
|
+
const sync = () => {
|
|
564
|
+
stop();
|
|
565
|
+
if (lost) return;
|
|
566
|
+
draw(0);
|
|
567
|
+
if (moving()) frame = requestAnimationFrame(tick);
|
|
568
|
+
};
|
|
569
|
+
canvas.addEventListener("webglcontextlost", (event) => {
|
|
570
|
+
event.preventDefault();
|
|
571
|
+
lost = true;
|
|
572
|
+
stop();
|
|
573
|
+
delete canvas.parentElement.dataset.ready;
|
|
574
|
+
canvas.dispatchEvent(new Event("cw-wave-fallback"));
|
|
575
|
+
});
|
|
576
|
+
canvas.addEventListener("webglcontextrestored", () => {
|
|
577
|
+
delete canvas.parentElement.dataset.ready;
|
|
578
|
+
lost = true;
|
|
579
|
+
});
|
|
580
|
+
addEventListener("resize", () => draw(0));
|
|
581
|
+
return {
|
|
582
|
+
draw,
|
|
583
|
+
sync,
|
|
584
|
+
stop,
|
|
585
|
+
get available() {
|
|
586
|
+
return !lost;
|
|
587
|
+
},
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
})();
|
package/dist/index.d.mts
CHANGED
|
@@ -36,6 +36,18 @@ interface DefaultThemeOptions {
|
|
|
36
36
|
export declare function createDefaultTheme(options?: DefaultThemeOptions): ExplorerTheme;
|
|
37
37
|
export declare const defaultTheme: ExplorerTheme;
|
|
38
38
|
//#endregion
|
|
39
|
+
//#region src/theme-crosswave.d.ts
|
|
40
|
+
type CrosswaveColor = "azure" | "violet" | "amber" | "rose" | "jade" | "graphite";
|
|
41
|
+
interface CrosswaveThemeOptions {
|
|
42
|
+
readonly color?: CrosswaveColor;
|
|
43
|
+
readonly backgroundMotion?: boolean;
|
|
44
|
+
readonly pageTransitions?: boolean;
|
|
45
|
+
readonly gamepad?: boolean;
|
|
46
|
+
readonly project?: ThemeProjectOptions;
|
|
47
|
+
}
|
|
48
|
+
/** PSP-inspired media navigation with original wave graphics and ordinary static links. */
|
|
49
|
+
export declare function createCrosswaveTheme(options?: CrosswaveThemeOptions): ExplorerTheme;
|
|
50
|
+
//#endregion
|
|
39
51
|
//#region src/theme-plain.d.ts
|
|
40
52
|
interface PlainThemeOptions {
|
|
41
53
|
readonly project?: ThemeProjectOptions;
|
|
@@ -43,5 +55,5 @@ interface PlainThemeOptions {
|
|
|
43
55
|
/** A no-icon, no-script theme that leaves navigation and controls to the browser. */
|
|
44
56
|
export declare function createPlainTheme(options?: PlainThemeOptions): ExplorerTheme;
|
|
45
57
|
//#endregion
|
|
46
|
-
export { type BreadcrumbItem, type BreadcrumbsProps, type DefaultThemeIconSet, type DefaultThemeIconVariant, type DefaultThemeRuntimeConfig, type DirectoryData, type DirwellConfig, type DirwellConfigContext, type DirwellConfigInput, type DirwellThemeComponentOverrides, type DirwellThemeComponents, type EmptyStateProps, type EntryKind, type EntryListItem, type EntryListProps, type EntryNavigation, type EntryRowProps, type ExplorerTheme, type FileMetadata, type FileSystemEntry, type FileTimes, type FooterProps, type GenerateOptions, type IconName, type IconProps, type NameSortMode, type OutputNameResolver, type PageShellProps, type PlainThemeOptions, type RenderedPage, type SortDirection, type SortField, type SortOptions, type SymlinkMetadata, type ThemeContext, type ToolbarProps, defineConfig, loadDirwellConfig, resolveGenerateOptions, resolveThemeComponents };
|
|
58
|
+
export { type BreadcrumbItem, type BreadcrumbsProps, type CrosswaveColor, type CrosswaveThemeOptions, type DefaultThemeIconSet, type DefaultThemeIconVariant, type DefaultThemeRuntimeConfig, type DirectoryData, type DirwellConfig, type DirwellConfigContext, type DirwellConfigInput, type DirwellThemeComponentOverrides, type DirwellThemeComponents, type EmptyStateProps, type EntryKind, type EntryListItem, type EntryListProps, type EntryNavigation, type EntryRowProps, type ExplorerTheme, type FileMetadata, type FileSystemEntry, type FileTimes, type FooterProps, type GenerateOptions, type IconName, type IconProps, type NameSortMode, type OutputNameResolver, type PageShellProps, type PlainThemeOptions, type RenderedPage, type SortDirection, type SortField, type SortOptions, type SymlinkMetadata, type ThemeContext, type ToolbarProps, defineConfig, loadDirwellConfig, resolveGenerateOptions, resolveThemeComponents };
|
|
47
59
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,188 @@
|
|
|
1
1
|
import { a as readGeneratedPage, c as resolveThemeProject, d as utcTimestamp, f as defineConfig, l as defaultThemeComponents, m as resolveGenerateOptions, n as defaultOutputName, o as createDefaultTheme, p as loadDirwellConfig, r as generateExplorer, s as defaultTheme, t as compareEntries, u as escapeHtml } from "./generator-CtX3iHhK.mjs";
|
|
2
2
|
import { t as resolveThemeComponents } from "./theme-components-CXzvdQY-.mjs";
|
|
3
3
|
import { t as createExplorerDevServer } from "./dev-server-Q7NC1wiB.mjs";
|
|
4
|
+
import { readFile } from "node:fs/promises";
|
|
5
|
+
//#region src/theme-crosswave/icons.ts
|
|
6
|
+
const paths = {
|
|
7
|
+
all: "<rect x=\"3\" y=\"3\" width=\"7\" height=\"7\" rx=\"1\"/><rect x=\"14\" y=\"3\" width=\"7\" height=\"7\" rx=\"1\"/><rect x=\"3\" y=\"14\" width=\"7\" height=\"7\" rx=\"1\"/><rect x=\"14\" y=\"14\" width=\"7\" height=\"7\" rx=\"1\"/>",
|
|
8
|
+
folder: "<path d=\"M3 7V5a1 1 0 0 1 1-1h5l2 3h9a1 1 0 0 1 1 1v11a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1Z\"/><path d=\"M3 9h18\"/>",
|
|
9
|
+
image: "<rect x=\"3\" y=\"4\" width=\"18\" height=\"16\" rx=\"1.5\"/><circle cx=\"8\" cy=\"9\" r=\"1.5\"/><path d=\"m3 17 5-5 4 4 4-6 5 7\"/>",
|
|
10
|
+
audio: "<path d=\"M9 18V5l11-2v13M9 8l11-2\"/><ellipse cx=\"6\" cy=\"18\" rx=\"3\" ry=\"2.5\"/><ellipse cx=\"17\" cy=\"16\" rx=\"3\" ry=\"2.5\"/>",
|
|
11
|
+
video: "<rect x=\"3\" y=\"4\" width=\"18\" height=\"16\" rx=\"1.5\"/><path d=\"m10 8 6 4-6 4Z\"/>",
|
|
12
|
+
document: "<path d=\"M5 3h9l5 5v13H5ZM14 3v5h5M8 12h8M8 16h6\"/>",
|
|
13
|
+
other: "<path d=\"m12 3 9 5v9l-9 5-9-5V8ZM3 8l9 5 9-5M12 13v9\"/>",
|
|
14
|
+
link: "<path d=\"m9 15 6-6M8 10l-3 3a4 4 0 0 0 6 6l3-3M10 8l3-3a4 4 0 0 1 6 6l-3 3\"/>",
|
|
15
|
+
back: "<path d=\"m10 5-7 7 7 7M3 12h18\"/>",
|
|
16
|
+
search: "<circle cx=\"10.5\" cy=\"10.5\" r=\"6.5\"/><path d=\"m16 16 5 5\"/>",
|
|
17
|
+
pause: "<path d=\"M8 5v14M16 5v14\"/>",
|
|
18
|
+
play: "<path d=\"m8 4 12 8-12 8Z\"/>",
|
|
19
|
+
controller: "<path d=\"M7 7h10c3 0 4 3 5 9 0 3-2 4-4 1l-2-2H8l-2 2c-2 3-4 2-4-1 1-6 2-9 5-9ZM6 11h4M8 9v4M16 10h.01M19 13h.01\"/>"
|
|
20
|
+
};
|
|
21
|
+
/** Original geometric icons, not copied console artwork. */
|
|
22
|
+
function crosswaveIcon(name) {
|
|
23
|
+
return `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.45" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${paths[name]}</svg>`;
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
//#region src/theme-crosswave/styles.ts
|
|
27
|
+
const crosswaveStyles = String.raw`
|
|
28
|
+
:root{color-scheme:dark;--cw-top:#2b4f85;--cw-bottom:#091a3a;--cw-text:#f7fbff;--cw-muted:#edf4fc;--cw-focus:#fff;--cw-ease:cubic-bezier(.16,1,.3,1);font-family:"Segoe UI",Helvetica,Arial,sans-serif;font-synthesis:none}
|
|
29
|
+
:root[data-cw-color=violet]{--cw-top:#5c407d;--cw-bottom:#241532;--cw-muted:#edf4fc}:root[data-cw-color=amber]{--cw-top:#704d1a;--cw-bottom:#30200b;--cw-muted:#edf4fc}:root[data-cw-color=rose]{--cw-top:#704055;--cw-bottom:#351b29;--cw-muted:#edf4fc}:root[data-cw-color=jade]{--cw-top:#215e52;--cw-bottom:#0b2a28;--cw-muted:#edf4fc}:root[data-cw-color=graphite]{--cw-top:#424d61;--cw-bottom:#181d28;--cw-muted:#edf4fc}
|
|
30
|
+
*{box-sizing:border-box}[hidden]{display:none!important}html{scroll-padding-bottom:110px;min-height:100%;background:var(--cw-bottom)}body{margin:0;color:var(--cw-text);background:var(--cw-bottom);min-height:100svh;font-size:16px;line-height:1.5}::selection{background:#d0e4fc;color:#132848}a{color:inherit;text-decoration:none}button,input,select{font:inherit;color:inherit}button,select{cursor:pointer}button{border:0}a:focus-visible,button:focus-visible,input:focus-visible,select:focus-visible{outline:2px solid var(--cw-focus);outline-offset:6px;border-radius:3px}svg{display:block;width:24px;height:24px;flex:none}.cw-sr{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.cw-skip{position:fixed;left:24px;top:16px;padding:12px 18px;background:#fff;color:#142e53;z-index:20;transform:translateY(-150%)}.cw-skip:focus{transform:none}
|
|
31
|
+
.cw-background{position:fixed;inset:0;z-index:0;pointer-events:none;background:radial-gradient(ellipse at 28% 4%,#c0d7ff12,transparent 68%),linear-gradient(165deg,var(--cw-top),var(--cw-bottom));view-transition-name:cw-background;overflow:hidden}.cw-background canvas{width:100%;height:100%;display:block}.cw-fallback-wave{position:absolute;inset:0;width:100%;height:100%;fill:none;stroke:#d5e8ff;stroke-width:18;opacity:.06;filter:blur(3px)}.cw-background[data-ready] .cw-fallback-wave{display:none}
|
|
32
|
+
.cw-top,.cw-stage,.cw-bottom,.cw-noscript{position:relative;z-index:1}.cw-top{display:flex;align-items:center;gap:32px;padding:30px 48px 20px;min-height:92px;view-transition-name:cw-header}.cw-brand{font-size:20px;font-weight:500;letter-spacing:-.02em;white-space:nowrap}.cw-brand span{display:block;font-size:12px;letter-spacing:.08em;color:var(--cw-muted);font-weight:400}.cw-location{display:flex;gap:12px;align-items:center;min-width:0;flex-wrap:wrap;font-size:13px;color:var(--cw-muted)}.cw-location a:hover{text-decoration:underline;text-underline-offset:5px}.cw-separator{opacity:.6}.cw-clock{margin-left:auto;white-space:nowrap;font-variant-numeric:tabular-nums;font-size:14px;color:var(--cw-muted)}
|
|
33
|
+
.cw-stage{max-width:1500px;margin:28px auto 110px;padding:0 72px;display:grid;grid-template-columns:minmax(0,1.55fr) minmax(220px,1fr);column-gap:80px}.cw-rail{grid-column:1/-1;margin:0 -12px 22px;overflow-x:auto;padding:12px 12px 24px;scrollbar-width:none}.cw-rail::-webkit-scrollbar{display:none}.cw-categories{padding-left:104px;padding-right:calc(100vw - 200px);display:flex;gap:32px;min-width:max-content;align-items:flex-start}.cw-category{position:relative;display:flex;flex-direction:column;align-items:center;gap:12px;min-width:94px;min-height:96px;background:none;padding:10px 4px;color:var(--cw-muted);transition:color .2s,transform .3s var(--cw-ease);flex:none}.cw-category svg{width:40px;height:40px;filter:drop-shadow(0 3px 2px #07182a55)}.cw-category span{font-size:14px;white-space:nowrap}.cw-category small{font-size:10px;position:absolute;right:8px;top:0;color:var(--cw-muted)}.cw-category:hover{color:#fff;transform:translateY(-3px)}.cw-category.cw-active{color:#fff;transform:scale(1.1)}.cw-category.cw-active:after{content:"";position:absolute;left:calc(50% - 12px);bottom:-8px;width:24px;height:2px;background:#e6f2ff;box-shadow:0 3px 10px #bddaff55}
|
|
34
|
+
.cw-browser{padding-left:104px;min-width:0;view-transition-name:cw-browser}.cw-list-head{display:flex;align-items:center;justify-content:space-between;gap:24px;min-height:58px;margin:0 0 12px;padding:0 4px}.cw-directory{display:flex;align-items:center;gap:16px;min-width:0}.cw-directory h2{font-size:17px;font-weight:400;margin:0;white-space:nowrap}.cw-directory>[data-cw-count]{font-size:12px;color:var(--cw-muted);white-space:nowrap}.cw-parent,.cw-parent-placeholder{width:34px;height:44px;display:flex;align-items:center;justify-content:center;flex:none}.cw-parent:hover{background:#ffffff12;border-radius:5px}.cw-search{display:flex;align-items:center;gap:8px;border-bottom:1px solid #d5e7ff55;min-width:140px;width:220px;padding:6px 0}.cw-search svg{width:17px;height:17px;color:var(--cw-muted)}.cw-search input{background:none;border:0;min-width:0;width:100%;outline-offset:3px;font-size:13px;caret-color:#fff}.cw-search input::placeholder{color:var(--cw-muted);opacity:1}.cw-search button{background:none;font-size:11px;padding:6px 0}.cw-search input::-webkit-search-cancel-button{display:none}
|
|
35
|
+
.cw-list-scroll{max-height:calc(100svh - 365px);min-height:220px;overflow-y:auto;overflow-x:hidden;scrollbar-width:thin;scrollbar-color:#d6e8fa66 transparent;padding:14px 12px 24px 8px;scroll-padding-block:20px;overscroll-behavior:contain}.cw-list-scroll::-webkit-scrollbar{width:5px}.cw-list-scroll::-webkit-scrollbar-thumb{background:#d6e8fa66;border-radius:4px}.cw-files{list-style:none;padding:0;margin:0}.cw-row{margin:0;min-height:76px;padding:0}.cw-entry{display:flex;align-items:center;gap:25px;padding:14px 18px;min-height:76px;transition:background .2s,opacity .2s,transform .3s var(--cw-ease);border-radius:7px}.cw-entry-icon{display:flex;width:42px;min-width:42px;align-items:center;justify-content:center;transition:transform .3s var(--cw-ease);filter:drop-shadow(0 3px 2px #08192c66)}.cw-entry-icon svg{width:34px;height:34px}.cw-entry-label{min-width:0;display:block}.cw-name{display:block;font-size:18px;font-weight:400;line-height:1.4;overflow-wrap:anywhere;text-shadow:0 2px 3px #07192b66}.cw-row-meta{display:block;font-size:11px;color:var(--cw-muted);margin-top:3px}.cw-row-date{display:none;font-size:11px;color:var(--cw-muted);margin-top:4px}.cw-target{display:block;font-size:11px;color:var(--cw-muted);overflow-wrap:anywhere;margin-top:4px}.cw-row.cw-selected .cw-entry{background:linear-gradient(90deg,#f4f9ff08,#f4f9ff04);transform:translateX(5px)}.cw-row.cw-selected .cw-entry-icon{transform:scale(1.22)}.cw-row.cw-selected .cw-name{color:#fff}.cw-row:hover .cw-entry{background:#eef7ff08}.cw-unavailable{color:var(--cw-muted)}.cw-notice{position:fixed;bottom:90px;left:48px;right:24px;z-index:3;margin:0;background:var(--cw-bottom);border-radius:4px;font-size:12px;color:var(--cw-muted);padding:12px 24px;max-width:52ch}.cw-empty{padding:36px 28px;color:var(--cw-muted);font-size:15px}.cw-detail{min-width:0;align-self:center;padding:20px 0 28px;view-transition-name:cw-detail}.cw-detail-symbol{margin-bottom:28px;filter:drop-shadow(0 12px 12px #04152e44)}.cw-detail-symbol svg{width:110px;height:110px;stroke-width:.8;opacity:.9}.cw-detail h2{font-size:25px;font-weight:300;margin:0 0 7px;overflow-wrap:anywhere;line-height:1.3;letter-spacing:-.015em}.cw-detail>p{font-size:12px;color:var(--cw-muted);margin:0 0 25px}.cw-detail dl{margin:0;font-size:12px}.cw-detail dl>div{display:grid;grid-template-columns:70px minmax(0,1fr);gap:10px;margin-bottom:10px}.cw-detail dt{color:var(--cw-muted)}.cw-detail dd{margin:0;overflow-wrap:anywhere}.cw-open{display:inline-flex;margin-top:24px;min-height:44px;align-items:center;font-size:14px;border-bottom:1px solid #e8f1ff88;gap:12px}.cw-open:hover{border-bottom-color:#fff}.cw-open:after{content:"";display:block;width:7px;height:7px;border-top:1px solid currentColor;border-right:1px solid currentColor;transform:rotate(45deg)}
|
|
36
|
+
.cw-bottom{display:flex;align-items:center;justify-content:space-between;gap:24px;padding:22px 48px;position:fixed;bottom:0;left:0;right:0;background:linear-gradient(0deg,var(--cw-bottom),transparent);min-height:82px}.cw-key-help{display:flex;gap:20px;flex-wrap:wrap;font-size:11px;color:var(--cw-muted)}kbd{font:inherit;color:#fff;margin-right:5px}.cw-appearance{display:flex;align-items:center;gap:16px;flex:none}.cw-appearance select{background:#0b193b70;border:1px solid #d7e9ff33;padding:7px 24px 7px 10px;border-radius:4px;max-width:116px;font-size:12px;min-height:36px}.cw-appearance button{display:flex;align-items:center;gap:8px;background:transparent;font-size:12px;min-height:40px;padding:6px 0}.cw-appearance button svg{width:16px;height:16px}.cw-controller{display:flex;align-items:center;gap:8px;font-size:11px;color:var(--cw-muted)}.cw-controller svg{width:22px;height:22px}.cw-noscript{padding:0 48px 24px;color:var(--cw-muted);font-size:13px}
|
|
37
|
+
body:not([data-cw-enhanced]) .cw-browser{padding-left:0}body:not([data-cw-enhanced]) .cw-stage{display:block;margin-top:20px}body:not([data-cw-enhanced]) .cw-list-scroll{max-height:none;min-height:0}body:not([data-cw-enhanced]) .cw-parent-placeholder{display:none}body:not([data-cw-enhanced]) .cw-bottom{display:none}
|
|
38
|
+
@view-transition{navigation:auto}::view-transition-old(root),::view-transition-new(root){animation:none}::view-transition-old(cw-background),::view-transition-new(cw-background){animation-duration:420ms}::view-transition-old(cw-browser),::view-transition-old(cw-detail){animation:cw-out 180ms ease-in both}::view-transition-new(cw-browser),::view-transition-new(cw-detail){animation:cw-in 420ms var(--cw-ease) both}::view-transition-old(cw-header),::view-transition-new(cw-header){animation-duration:240ms}@keyframes cw-out{to{opacity:0;transform:translateX(-22px);filter:blur(3px)}}@keyframes cw-in{from{opacity:0;transform:translateX(32px);filter:blur(5px)}}:root[data-cw-transitions=false]::view-transition-group(*){animation-duration:0s}:root[data-cw-transitions=false]::view-transition-old(*),:root[data-cw-transitions=false]::view-transition-new(*){animation:none}.cw-category-changing .cw-files{animation:cw-category-in 260ms var(--cw-ease)}@keyframes cw-category-in{from{opacity:.35;transform:translateX(12px)}}
|
|
39
|
+
@media(min-width:1500px){.cw-stage{margin-top:55px}.cw-categories{gap:48px}.cw-category{min-width:112px}.cw-list-scroll{max-height:calc(100svh - 405px)}}
|
|
40
|
+
@media(max-width:1000px){.cw-top{padding:24px 30px;gap:22px}.cw-stage{padding:0 36px;column-gap:35px}.cw-categories{padding-left:44px;gap:20px}.cw-browser{padding-left:44px}.cw-category{min-width:82px}.cw-list-head{flex-wrap:wrap;gap:8px}.cw-search{margin-left:52px;width:calc(100% - 52px)}.cw-list-scroll{max-height:calc(100svh - 390px)}.cw-bottom{padding:20px 30px}.cw-key-help{gap:12px}.cw-detail h2{font-size:21px}.cw-clock{font-size:12px}}
|
|
41
|
+
@media(max-width:680px){.cw-top{padding:22px 20px 12px;flex-wrap:wrap;gap:16px;min-height:104px}.cw-brand{font-size:18px}.cw-brand span{font-size:10px}.cw-clock{order:1;font-size:11px}.cw-location{order:2;width:100%;font-size:12px;gap:8px}.cw-stage{padding:0 16px;margin:12px auto 100px;display:block}.cw-rail{margin:0 -8px 8px;padding:12px 8px 20px}.cw-categories{padding-left:0;gap:14px}.cw-browser{padding-left:0}.cw-category{min-width:76px;min-height:83px;padding:10px 2px;gap:10px}.cw-category svg{width:33px;height:33px}.cw-category span{font-size:12px}.cw-category small{right:5px}.cw-list-head{padding:0 12px;margin-bottom:8px}.cw-directory{gap:12px}.cw-directory h2{font-size:16px}.cw-search{margin-left:46px;width:calc(100% - 46px);min-height:44px}.cw-search input,.cw-search button{min-height:44px}.cw-list-scroll{max-height:none;min-height:150px;padding:8px 2px 18px}.cw-entry{gap:20px;padding:14px 12px;min-height:78px}.cw-entry-icon{width:36px;min-width:36px}.cw-entry-icon svg{width:29px;height:29px}.cw-name{font-size:17px}.cw-detail{display:none!important}.cw-row-date{display:block}.cw-bottom{padding:16px 20px;min-height:76px;gap:12px}.cw-key-help{display:none!important}.cw-appearance{width:100%;justify-content:space-between}.cw-appearance select,.cw-appearance button{min-height:44px}.cw-controller{position:absolute;bottom:72px;right:20px;background:var(--cw-bottom);padding:6px 9px;border-radius:4px;max-width:calc(100% - 40px)}.cw-notice{left:20px;right:20px;bottom:90px}.cw-noscript{padding:0 24px 24px}}
|
|
42
|
+
@media(max-height:520px){.cw-stage{grid-template-columns:minmax(0,1fr)}.cw-detail{display:none!important}.cw-row-date{display:block}.cw-top{flex-wrap:nowrap}.cw-location{order:0;width:auto}.cw-clock{display:none!important}.cw-top{min-height:58px;padding-top:10px;padding-bottom:8px}.cw-stage{margin-top:0;margin-bottom:70px}.cw-rail{margin-bottom:0;padding-bottom:12px}.cw-category{min-height:68px;gap:4px;padding-top:4px}.cw-category svg{width:28px;height:28px}.cw-list-scroll{max-height:calc(100svh - 235px);min-height:80px}.cw-list-head{min-height:36px;margin-bottom:0}.cw-detail-symbol{margin-bottom:10px}.cw-detail-symbol svg{width:60px;height:60px}.cw-detail>p{margin-bottom:12px}.cw-bottom{min-height:60px;padding-block:10px}.cw-list-head{flex-wrap:nowrap}.cw-search{margin-left:0;width:170px}.cw-detail dl>div{margin-bottom:4px}}
|
|
43
|
+
@media(prefers-reduced-motion:reduce){*,*:before,*:after{scroll-behavior:auto!important;animation:none!important;transition:none!important}::view-transition-group(*),::view-transition-old(*),::view-transition-new(*){animation:none!important}.cw-row.cw-selected .cw-entry,.cw-category.cw-active,.cw-category:hover{transform:none}}
|
|
44
|
+
`;
|
|
45
|
+
//#endregion
|
|
46
|
+
//#region src/theme-crosswave.ts
|
|
47
|
+
const runtime = await readFile(new URL("./crosswave-runtime.js", import.meta.url), "utf8");
|
|
48
|
+
const categories = [
|
|
49
|
+
["all", "All files"],
|
|
50
|
+
["folder", "Folders"],
|
|
51
|
+
["image", "Photos"],
|
|
52
|
+
["audio", "Music"],
|
|
53
|
+
["video", "Videos"],
|
|
54
|
+
["document", "Documents"],
|
|
55
|
+
["other", "Other"]
|
|
56
|
+
];
|
|
57
|
+
const extensions = Object.fromEntries([
|
|
58
|
+
...[
|
|
59
|
+
"png",
|
|
60
|
+
"jpg",
|
|
61
|
+
"jpeg",
|
|
62
|
+
"webp",
|
|
63
|
+
"gif",
|
|
64
|
+
"avif",
|
|
65
|
+
"svg",
|
|
66
|
+
"bmp",
|
|
67
|
+
"heic"
|
|
68
|
+
].map((x) => [x, "image"]),
|
|
69
|
+
...[
|
|
70
|
+
"mp3",
|
|
71
|
+
"wav",
|
|
72
|
+
"ogg",
|
|
73
|
+
"flac",
|
|
74
|
+
"m4a",
|
|
75
|
+
"aac",
|
|
76
|
+
"aiff",
|
|
77
|
+
"opus"
|
|
78
|
+
].map((x) => [x, "audio"]),
|
|
79
|
+
...[
|
|
80
|
+
"mp4",
|
|
81
|
+
"webm",
|
|
82
|
+
"mov",
|
|
83
|
+
"mkv",
|
|
84
|
+
"avi",
|
|
85
|
+
"m4v"
|
|
86
|
+
].map((x) => [x, "video"]),
|
|
87
|
+
...[
|
|
88
|
+
"txt",
|
|
89
|
+
"md",
|
|
90
|
+
"pdf",
|
|
91
|
+
"json",
|
|
92
|
+
"csv",
|
|
93
|
+
"html",
|
|
94
|
+
"xml",
|
|
95
|
+
"doc",
|
|
96
|
+
"docx",
|
|
97
|
+
"rtf",
|
|
98
|
+
"yaml",
|
|
99
|
+
"yml"
|
|
100
|
+
].map((x) => [x, "document"])
|
|
101
|
+
]);
|
|
102
|
+
function category(entry) {
|
|
103
|
+
if (entry.kind === "directory" || entry.symlink?.targetKind === "directory") return "folder";
|
|
104
|
+
return extensions[entry.name.split(".").at(-1)?.toLowerCase() ?? ""] ?? "other";
|
|
105
|
+
}
|
|
106
|
+
function size(bytes) {
|
|
107
|
+
const units = [
|
|
108
|
+
"B",
|
|
109
|
+
"KiB",
|
|
110
|
+
"MiB",
|
|
111
|
+
"GiB",
|
|
112
|
+
"TiB"
|
|
113
|
+
];
|
|
114
|
+
const index = bytes > 0 ? Math.min(4, Math.floor(Math.log(bytes) / Math.log(1024))) : 0;
|
|
115
|
+
return `${(bytes / 1024 ** index).toLocaleString("en-US", { maximumFractionDigits: index === 0 ? 0 : 1 })} ${units[index]}`;
|
|
116
|
+
}
|
|
117
|
+
function status$1(entry) {
|
|
118
|
+
if (entry.symlink?.isBroken) return "Broken link · declared target only";
|
|
119
|
+
if (entry.symlink?.isOutsideRoot) return "Outside root · declared target only";
|
|
120
|
+
if (entry.symlink?.isTargetExcluded) return "Excluded target · declared target only";
|
|
121
|
+
if (entry.symlink?.isCycle) return "Directory link · cycle";
|
|
122
|
+
if (entry.kind === "symlink") return "Symbolic link";
|
|
123
|
+
if (entry.kind === "directory") return "Folder";
|
|
124
|
+
return "File";
|
|
125
|
+
}
|
|
126
|
+
function row(entry, context, index) {
|
|
127
|
+
const href = context.hrefFor(entry);
|
|
128
|
+
const type = category(entry);
|
|
129
|
+
const icon = entry.kind === "symlink" ? "link" : type;
|
|
130
|
+
const label = `${entry.name}${type === "folder" ? "/" : ""}`;
|
|
131
|
+
const unavailable = entry.symlink?.isOutsideRoot || entry.symlink?.isTargetExcluded || entry.symlink?.isBroken;
|
|
132
|
+
const bytes = type === "folder" ? "Folder" : size(entry.metadata.size);
|
|
133
|
+
const instant = new Date(entry.metadata.times.modifiedAt);
|
|
134
|
+
const modified = Number.isNaN(instant.getTime()) ? "Unknown" : `${instant.toISOString().slice(0, 16).replace("T", " ")} UTC`;
|
|
135
|
+
const target = entry.symlink === null ? "" : `<span class="cw-target">${escapeHtml(entry.symlink.target)}</span>`;
|
|
136
|
+
const data = `data-cw-entry data-category="${type}" data-icon="${icon}" data-name="${escapeHtml(entry.name)}" data-path="${escapeHtml(entry.relativePath)}" data-size="${escapeHtml(bytes)}" data-modified="${modified}" data-status="${escapeHtml(status$1(entry))}" data-exits="${context.exitsExplorerFor(entry)}"`;
|
|
137
|
+
const contents = `<span class="cw-entry-icon">${crosswaveIcon(icon)}</span><span class="cw-entry-label"><span class="cw-name">${escapeHtml(label)}</span>${target}<span class="cw-row-meta">${escapeHtml(status$1(entry))}${type === "folder" || unavailable ? "" : ` · ${bytes}`}</span><span class="cw-row-date">Modified ${modified}</span></span>`;
|
|
138
|
+
return `<li class="cw-row${index === 0 ? " cw-selected" : ""}" ${data}>${href === null ? `<span class="cw-entry cw-unavailable">${contents}</span>` : `<a class="cw-entry" href="${escapeHtml(href)}"${context.exitsExplorerFor(entry) ? " target=\"_blank\" rel=\"noopener\"" : " data-cw-navigation"}>${contents}</a>`}</li>`;
|
|
139
|
+
}
|
|
140
|
+
/** PSP-inspired media navigation with original wave graphics and ordinary static links. */
|
|
141
|
+
function createCrosswaveTheme(options = {}) {
|
|
142
|
+
const color = options.color ?? "azure";
|
|
143
|
+
if (![
|
|
144
|
+
"azure",
|
|
145
|
+
"violet",
|
|
146
|
+
"amber",
|
|
147
|
+
"rose",
|
|
148
|
+
"jade",
|
|
149
|
+
"graphite"
|
|
150
|
+
].includes(color)) throw new TypeError("Unknown Crosswave color");
|
|
151
|
+
for (const key of [
|
|
152
|
+
"backgroundMotion",
|
|
153
|
+
"pageTransitions",
|
|
154
|
+
"gamepad"
|
|
155
|
+
]) if (options[key] !== void 0 && typeof options[key] !== "boolean") throw new TypeError(`${key} must be a boolean`);
|
|
156
|
+
const project = resolveThemeProject(options.project);
|
|
157
|
+
return {
|
|
158
|
+
name: "crosswave",
|
|
159
|
+
searchIndex: false,
|
|
160
|
+
render(context) {
|
|
161
|
+
const { directory } = context;
|
|
162
|
+
const path = directory.current.relativePath || "/";
|
|
163
|
+
const parent = directory.parent === null ? null : context.hrefForDirectory(directory.parent.relativePath);
|
|
164
|
+
const segments = directory.current.relativePath.split("/").filter(Boolean);
|
|
165
|
+
const breadcrumbs = segments.map((segment, i) => i === segments.length - 1 ? `<span aria-current="page">${escapeHtml(segment)}</span>` : `<a data-cw-navigation href="${escapeHtml(context.hrefForDirectory(segments.slice(0, i + 1).join("/")))}">${escapeHtml(segment)}</a>`).join("<span class=\"cw-separator\" aria-hidden=\"true\">/</span>");
|
|
166
|
+
const tabs = categories.map(([type, label], i) => `<button role="tab" id="cw-tab-${type}" aria-selected="${i === 0}" aria-controls="cw-panel" tabindex="${i === 0 ? 0 : -1}" data-cw-category="${type}" class="cw-category${i === 0 ? " cw-active" : ""}">${crosswaveIcon(type)}<span>${label}</span><small data-cw-category-count="${type}"></small></button>`).join("");
|
|
167
|
+
return {
|
|
168
|
+
html: `<!doctype html>
|
|
169
|
+
<html lang="en" data-cw-color="${color}" data-cw-motion="${options.backgroundMotion !== false}" data-cw-transitions="${options.pageTransitions !== false}" data-cw-gamepad="${options.gamepad !== false}"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">${context.documentBaseHref === null ? "" : `<base href="${escapeHtml(context.documentBaseHref)}">`}<title>${escapeHtml(path)} · ${escapeHtml(project.name)} · Crosswave</title><link rel="stylesheet" href="${escapeHtml(context.assetHref("crosswave.css"))}"><script src="${escapeHtml(context.assetHref("crosswave.js"))}"><\/script></head>
|
|
170
|
+
<body><a class="cw-skip" href="${escapeHtml(context.hrefForDirectory(directory.current.relativePath))}#cw-panel">Skip to files</a><div class="cw-background" aria-hidden="true"><canvas id="cw-wave"></canvas><svg class="cw-fallback-wave" viewBox="0 0 1440 900" preserveAspectRatio="none"><path d="M-80 570C220 300 370 690 760 450S1260 290 1510 510"/><path d="M-80 540C220 640 400 330 820 470S1240 620 1510 360"/></svg></div>
|
|
171
|
+
<header class="cw-top"><a class="cw-brand" data-cw-navigation href="${escapeHtml(context.hrefForDirectory(""))}">${escapeHtml(project.name)}<span>Crosswave</span></a><nav class="cw-location" aria-label="Location"><a data-cw-navigation href="${escapeHtml(context.hrefForDirectory(""))}">Home</a>${breadcrumbs ? `<span class="cw-separator" aria-hidden="true">/</span>${breadcrumbs}` : ""}</nav><time class="cw-clock" aria-label="Current time" hidden></time></header>
|
|
172
|
+
<main class="cw-stage" data-cw-root="${escapeHtml(context.hrefForDirectory(""))}"><h1 class="cw-sr">Files in ${escapeHtml(path)}</h1><nav class="cw-rail" aria-label="File categories" hidden><div class="cw-categories" role="tablist" aria-label="File categories">${tabs}</div></nav>
|
|
173
|
+
<section class="cw-browser" id="cw-panel" aria-label="Files"><div class="cw-list-head"><div class="cw-directory">${parent === null ? "<span class=\"cw-parent-placeholder\"></span>" : `<a class="cw-parent" data-cw-parent data-cw-navigation href="${escapeHtml(parent)}" aria-label="Parent directory">${crosswaveIcon("back")}</a>`}<h2 data-cw-heading>All files</h2><span data-cw-count>${directory.entries.length} items</span></div><div class="cw-search" hidden>${crosswaveIcon("search")}<label class="cw-sr" for="cw-search">Search this folder</label><input id="cw-search" type="search" placeholder="Search this folder" autocomplete="off" spellcheck="false"><button type="button" data-cw-clear hidden>Clear</button></div></div>
|
|
174
|
+
<div class="cw-list-scroll"><ul class="cw-files">${directory.entries.map((entry, i) => row(entry, context, i)).join("")}</ul><p class="cw-empty" data-cw-empty${directory.entries.length ? " hidden" : ""}>${directory.entries.length ? "No files match this search." : "This folder is empty."}</p></div><p class="cw-sr" role="status" aria-live="polite" data-cw-status></p></section>
|
|
175
|
+
<aside class="cw-detail" aria-label="Selected file details" hidden><div class="cw-detail-symbol" data-cw-detail-icon></div><h2 data-cw-detail-name></h2><p data-cw-detail-status></p><dl><div><dt>Path</dt><dd data-cw-detail-path></dd></div><div><dt>Size</dt><dd data-cw-detail-size></dd></div><div><dt>Modified</dt><dd data-cw-detail-modified></dd></div></dl><a class="cw-open" data-cw-open>Open file</a></aside></main>
|
|
176
|
+
<footer class="cw-bottom"><span class="cw-key-help" hidden><span><kbd>Arrow keys</kbd> Navigate</span><span><kbd>Enter</kbd> Open</span><span><kbd>Backspace</kbd> Parent</span></span><span class="cw-controller" role="status" hidden>${crosswaveIcon("controller")}<span data-cw-controller-label></span></span><div class="cw-appearance" hidden><label class="cw-sr" for="cw-color">Background color</label><select id="cw-color" aria-label="Background color"><option value="azure">Azure</option><option value="violet">Violet</option><option value="amber">Amber</option><option value="rose">Rose</option><option value="jade">Jade</option><option value="graphite">Graphite</option></select><button data-cw-motion aria-pressed="false" type="button">${crosswaveIcon("pause")}<span>Pause waves</span></button></div></footer><noscript><p class="cw-noscript">All files are available below. Categories, search, animated waves, and controller navigation need JavaScript.</p></noscript></body></html>`,
|
|
177
|
+
assets: {
|
|
178
|
+
"crosswave.css": crosswaveStyles,
|
|
179
|
+
"crosswave.js": runtime
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
//#endregion
|
|
4
186
|
//#region src/theme-plain.ts
|
|
5
187
|
function escapeHtml$1(value) {
|
|
6
188
|
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """);
|
|
@@ -69,6 +251,6 @@ function createPlainTheme(options = {}) {
|
|
|
69
251
|
};
|
|
70
252
|
}
|
|
71
253
|
//#endregion
|
|
72
|
-
export { compareEntries, createDefaultTheme, createExplorerDevServer, createPlainTheme, defaultOutputName, defaultTheme, defaultThemeComponents, defineConfig, escapeHtml, generateExplorer, loadDirwellConfig, readGeneratedPage, resolveGenerateOptions, resolveThemeComponents };
|
|
254
|
+
export { compareEntries, createCrosswaveTheme, createDefaultTheme, createExplorerDevServer, createPlainTheme, defaultOutputName, defaultTheme, defaultThemeComponents, defineConfig, escapeHtml, generateExplorer, loadDirwellConfig, readGeneratedPage, resolveGenerateOptions, resolveThemeComponents };
|
|
73
255
|
|
|
74
256
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["escapeHtml"],"sources":["../src/theme-plain.ts"],"sourcesContent":["import type { ExplorerTheme, FileSystemEntry, ThemeContext } from \"./model.ts\";\nimport { resolveThemeProject, type ThemeProjectOptions } from \"./theme-project.ts\";\nimport { utcTimestamp } from \"./timestamp.ts\";\n\nexport interface PlainThemeOptions {\n readonly project?: ThemeProjectOptions;\n}\n\nfunction escapeHtml(value: string): string {\n return value\n .replaceAll(\"&\", \"&\")\n .replaceAll(\"<\", \"<\")\n .replaceAll(\">\", \">\")\n .replaceAll('\"', \""\");\n}\n\nfunction externalHref(value: string | undefined): string | null {\n if (value === undefined) return null;\n try {\n const url = new URL(value);\n return url.protocol === \"https:\" || url.protocol === \"http:\" ? url.href : null;\n } catch {\n return null;\n }\n}\n\nfunction externalLink(label: string, href: string | null): string {\n return href === null\n ? escapeHtml(label)\n : `<a href=\"${escapeHtml(href)}\" target=\"_blank\" rel=\"noopener\">${escapeHtml(label)}</a>`;\n}\n\nfunction status(entry: FileSystemEntry): string {\n if (entry.symlink?.isBroken) return \"broken link\";\n if (entry.symlink?.isOutsideRoot) return \"unavailable link\";\n if (entry.symlink?.isCycle) return \"cycle link\";\n if (entry.kind === \"symlink\") return \"link\";\n if (entry.kind === \"directory\") return \"folder\";\n return \"file\";\n}\n\nfunction renderBreadcrumbs(context: ThemeContext): string {\n const segments = context.directory.current.relativePath.split(\"/\").filter(Boolean);\n if (segments.length === 0) return \"\";\n const ancestors = segments.slice(0, -1).map((segment, index) => {\n const relativePath = segments.slice(0, index + 1).join(\"/\");\n return `<a href=\"${escapeHtml(context.hrefForDirectory(relativePath))}\">${escapeHtml(segment)}</a>`;\n });\n return `<nav aria-label=\"Breadcrumb\"><a href=\"${escapeHtml(context.hrefForDirectory(\"\"))}\">Home</a> / ${ancestors.length === 0 ? \"\" : `${ancestors.join(\" / \")} / `}<span aria-current=\"page\">${escapeHtml(segments.at(-1) ?? \"\")}</span></nav>`;\n}\n\nfunction renderEntry(entry: FileSystemEntry, context: ThemeContext): string {\n const directoryLike = entry.kind === \"directory\" || entry.symlink?.targetKind === \"directory\";\n const name = `${escapeHtml(entry.name)}${directoryLike ? \"/\" : \"\"}`;\n const href = context.hrefFor(entry);\n const exitsExplorer = context.exitsExplorerFor(entry);\n const link =\n href === null\n ? `<span>${name}</span>`\n : `<a href=\"${escapeHtml(href)}\"${exitsExplorer ? ' target=\"_blank\" rel=\"noopener\"' : \"\"}>${name}</a>`;\n const details = [status(entry)];\n if (!directoryLike) details.push(`${entry.metadata.size} B`);\n if (entry.symlink !== null) details.push(`Target: ${entry.symlink.target}`);\n if (exitsExplorer) details.push(\"opens in a new tab\");\n const modified = utcTimestamp(entry.metadata.times.modifiedAt);\n return `<li>${link}<small>${escapeHtml(details.join(\" · \"))} · ${modified === null ? \"Unknown\" : `<time datetime=\"${modified.datetime}\">${modified.label}</time>`}</small></li>`;\n}\n\n/** A no-icon, no-script theme that leaves navigation and controls to the browser. */\nexport function createPlainTheme(options: PlainThemeOptions = {}): ExplorerTheme {\n const project = resolveThemeProject(options.project);\n const repositoryHref = externalHref(project.repositoryUrl);\n const licenseHref = externalHref(\n options.project?.licenseUrl ??\n (options.project?.license === undefined ? \"https://opensource.org/license/mit\" : undefined),\n );\n return {\n name: \"plain\",\n searchIndex: false,\n render(context) {\n const visiblePath =\n context.directory.current.relativePath === \"\"\n ? \"/\"\n : `/${context.directory.current.relativePath}/`;\n const parentHref =\n context.directory.parent === null\n ? null\n : context.hrefForDirectory(context.directory.parent.relativePath);\n const parentRow =\n parentHref === null ? \"\" : `<li><a href=\"${escapeHtml(parentHref)}\">../</a></li>`;\n const rows = context.directory.entries.map((entry) => renderEntry(entry, context)).join(\"\");\n const count = context.directory.entries.length;\n const footer = `<footer><hr><p>Repository: ${externalLink(project.name, repositoryHref)} by ${escapeHtml(project.author)} · ${externalLink(project.license, licenseHref)}</p></footer>`;\n const html = `<!doctype html>\n<html lang=\"en\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"><meta name=\"color-scheme\" content=\"light\">${context.documentBaseHref === null ? \"\" : `<base href=\"${escapeHtml(context.documentBaseHref)}\">`}<title>Index of ${escapeHtml(visiblePath)}</title><style>body{max-width:72ch;margin:2rem auto;padding:0 1rem}h1,li,nav{overflow-wrap:anywhere}li{margin:.6rem 0}small{display:block}</style></head>\n<body><main>${renderBreadcrumbs(context)}<h1>Index of ${escapeHtml(visiblePath)}</h1><p>${count} ${count === 1 ? \"entry\" : \"entries\"}</p>${count === 0 ? \"<p>This directory is empty.</p>\" : \"\"}<ul>${parentRow}${rows}</ul></main>${footer}</body></html>`;\n return { html };\n },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["status","escapeHtml"],"sources":["../src/theme-crosswave/icons.ts","../src/theme-crosswave/styles.ts","../src/theme-crosswave.ts","../src/theme-plain.ts"],"sourcesContent":["export type CrosswaveIcon =\n | \"all\"\n | \"folder\"\n | \"image\"\n | \"audio\"\n | \"video\"\n | \"document\"\n | \"other\"\n | \"link\"\n | \"back\"\n | \"search\"\n | \"pause\"\n | \"play\"\n | \"controller\";\n\nconst paths: Record<CrosswaveIcon, string> = {\n all: '<rect x=\"3\" y=\"3\" width=\"7\" height=\"7\" rx=\"1\"/><rect x=\"14\" y=\"3\" width=\"7\" height=\"7\" rx=\"1\"/><rect x=\"3\" y=\"14\" width=\"7\" height=\"7\" rx=\"1\"/><rect x=\"14\" y=\"14\" width=\"7\" height=\"7\" rx=\"1\"/>',\n folder:\n '<path d=\"M3 7V5a1 1 0 0 1 1-1h5l2 3h9a1 1 0 0 1 1 1v11a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1Z\"/><path d=\"M3 9h18\"/>',\n image:\n '<rect x=\"3\" y=\"4\" width=\"18\" height=\"16\" rx=\"1.5\"/><circle cx=\"8\" cy=\"9\" r=\"1.5\"/><path d=\"m3 17 5-5 4 4 4-6 5 7\"/>',\n audio:\n '<path d=\"M9 18V5l11-2v13M9 8l11-2\"/><ellipse cx=\"6\" cy=\"18\" rx=\"3\" ry=\"2.5\"/><ellipse cx=\"17\" cy=\"16\" rx=\"3\" ry=\"2.5\"/>',\n video: '<rect x=\"3\" y=\"4\" width=\"18\" height=\"16\" rx=\"1.5\"/><path d=\"m10 8 6 4-6 4Z\"/>',\n document: '<path d=\"M5 3h9l5 5v13H5ZM14 3v5h5M8 12h8M8 16h6\"/>',\n other: '<path d=\"m12 3 9 5v9l-9 5-9-5V8ZM3 8l9 5 9-5M12 13v9\"/>',\n link: '<path d=\"m9 15 6-6M8 10l-3 3a4 4 0 0 0 6 6l3-3M10 8l3-3a4 4 0 0 1 6 6l-3 3\"/>',\n back: '<path d=\"m10 5-7 7 7 7M3 12h18\"/>',\n search: '<circle cx=\"10.5\" cy=\"10.5\" r=\"6.5\"/><path d=\"m16 16 5 5\"/>',\n pause: '<path d=\"M8 5v14M16 5v14\"/>',\n play: '<path d=\"m8 4 12 8-12 8Z\"/>',\n controller:\n '<path d=\"M7 7h10c3 0 4 3 5 9 0 3-2 4-4 1l-2-2H8l-2 2c-2 3-4 2-4-1 1-6 2-9 5-9ZM6 11h4M8 9v4M16 10h.01M19 13h.01\"/>',\n};\n\n/** Original geometric icons, not copied console artwork. */\nexport function crosswaveIcon(name: CrosswaveIcon): string {\n return `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.45\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">${paths[name]}</svg>`;\n}\n","export const crosswaveStyles = String.raw`\n:root{color-scheme:dark;--cw-top:#2b4f85;--cw-bottom:#091a3a;--cw-text:#f7fbff;--cw-muted:#edf4fc;--cw-focus:#fff;--cw-ease:cubic-bezier(.16,1,.3,1);font-family:\"Segoe UI\",Helvetica,Arial,sans-serif;font-synthesis:none}\n:root[data-cw-color=violet]{--cw-top:#5c407d;--cw-bottom:#241532;--cw-muted:#edf4fc}:root[data-cw-color=amber]{--cw-top:#704d1a;--cw-bottom:#30200b;--cw-muted:#edf4fc}:root[data-cw-color=rose]{--cw-top:#704055;--cw-bottom:#351b29;--cw-muted:#edf4fc}:root[data-cw-color=jade]{--cw-top:#215e52;--cw-bottom:#0b2a28;--cw-muted:#edf4fc}:root[data-cw-color=graphite]{--cw-top:#424d61;--cw-bottom:#181d28;--cw-muted:#edf4fc}\n*{box-sizing:border-box}[hidden]{display:none!important}html{scroll-padding-bottom:110px;min-height:100%;background:var(--cw-bottom)}body{margin:0;color:var(--cw-text);background:var(--cw-bottom);min-height:100svh;font-size:16px;line-height:1.5}::selection{background:#d0e4fc;color:#132848}a{color:inherit;text-decoration:none}button,input,select{font:inherit;color:inherit}button,select{cursor:pointer}button{border:0}a:focus-visible,button:focus-visible,input:focus-visible,select:focus-visible{outline:2px solid var(--cw-focus);outline-offset:6px;border-radius:3px}svg{display:block;width:24px;height:24px;flex:none}.cw-sr{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.cw-skip{position:fixed;left:24px;top:16px;padding:12px 18px;background:#fff;color:#142e53;z-index:20;transform:translateY(-150%)}.cw-skip:focus{transform:none}\n.cw-background{position:fixed;inset:0;z-index:0;pointer-events:none;background:radial-gradient(ellipse at 28% 4%,#c0d7ff12,transparent 68%),linear-gradient(165deg,var(--cw-top),var(--cw-bottom));view-transition-name:cw-background;overflow:hidden}.cw-background canvas{width:100%;height:100%;display:block}.cw-fallback-wave{position:absolute;inset:0;width:100%;height:100%;fill:none;stroke:#d5e8ff;stroke-width:18;opacity:.06;filter:blur(3px)}.cw-background[data-ready] .cw-fallback-wave{display:none}\n.cw-top,.cw-stage,.cw-bottom,.cw-noscript{position:relative;z-index:1}.cw-top{display:flex;align-items:center;gap:32px;padding:30px 48px 20px;min-height:92px;view-transition-name:cw-header}.cw-brand{font-size:20px;font-weight:500;letter-spacing:-.02em;white-space:nowrap}.cw-brand span{display:block;font-size:12px;letter-spacing:.08em;color:var(--cw-muted);font-weight:400}.cw-location{display:flex;gap:12px;align-items:center;min-width:0;flex-wrap:wrap;font-size:13px;color:var(--cw-muted)}.cw-location a:hover{text-decoration:underline;text-underline-offset:5px}.cw-separator{opacity:.6}.cw-clock{margin-left:auto;white-space:nowrap;font-variant-numeric:tabular-nums;font-size:14px;color:var(--cw-muted)}\n.cw-stage{max-width:1500px;margin:28px auto 110px;padding:0 72px;display:grid;grid-template-columns:minmax(0,1.55fr) minmax(220px,1fr);column-gap:80px}.cw-rail{grid-column:1/-1;margin:0 -12px 22px;overflow-x:auto;padding:12px 12px 24px;scrollbar-width:none}.cw-rail::-webkit-scrollbar{display:none}.cw-categories{padding-left:104px;padding-right:calc(100vw - 200px);display:flex;gap:32px;min-width:max-content;align-items:flex-start}.cw-category{position:relative;display:flex;flex-direction:column;align-items:center;gap:12px;min-width:94px;min-height:96px;background:none;padding:10px 4px;color:var(--cw-muted);transition:color .2s,transform .3s var(--cw-ease);flex:none}.cw-category svg{width:40px;height:40px;filter:drop-shadow(0 3px 2px #07182a55)}.cw-category span{font-size:14px;white-space:nowrap}.cw-category small{font-size:10px;position:absolute;right:8px;top:0;color:var(--cw-muted)}.cw-category:hover{color:#fff;transform:translateY(-3px)}.cw-category.cw-active{color:#fff;transform:scale(1.1)}.cw-category.cw-active:after{content:\"\";position:absolute;left:calc(50% - 12px);bottom:-8px;width:24px;height:2px;background:#e6f2ff;box-shadow:0 3px 10px #bddaff55}\n.cw-browser{padding-left:104px;min-width:0;view-transition-name:cw-browser}.cw-list-head{display:flex;align-items:center;justify-content:space-between;gap:24px;min-height:58px;margin:0 0 12px;padding:0 4px}.cw-directory{display:flex;align-items:center;gap:16px;min-width:0}.cw-directory h2{font-size:17px;font-weight:400;margin:0;white-space:nowrap}.cw-directory>[data-cw-count]{font-size:12px;color:var(--cw-muted);white-space:nowrap}.cw-parent,.cw-parent-placeholder{width:34px;height:44px;display:flex;align-items:center;justify-content:center;flex:none}.cw-parent:hover{background:#ffffff12;border-radius:5px}.cw-search{display:flex;align-items:center;gap:8px;border-bottom:1px solid #d5e7ff55;min-width:140px;width:220px;padding:6px 0}.cw-search svg{width:17px;height:17px;color:var(--cw-muted)}.cw-search input{background:none;border:0;min-width:0;width:100%;outline-offset:3px;font-size:13px;caret-color:#fff}.cw-search input::placeholder{color:var(--cw-muted);opacity:1}.cw-search button{background:none;font-size:11px;padding:6px 0}.cw-search input::-webkit-search-cancel-button{display:none}\n.cw-list-scroll{max-height:calc(100svh - 365px);min-height:220px;overflow-y:auto;overflow-x:hidden;scrollbar-width:thin;scrollbar-color:#d6e8fa66 transparent;padding:14px 12px 24px 8px;scroll-padding-block:20px;overscroll-behavior:contain}.cw-list-scroll::-webkit-scrollbar{width:5px}.cw-list-scroll::-webkit-scrollbar-thumb{background:#d6e8fa66;border-radius:4px}.cw-files{list-style:none;padding:0;margin:0}.cw-row{margin:0;min-height:76px;padding:0}.cw-entry{display:flex;align-items:center;gap:25px;padding:14px 18px;min-height:76px;transition:background .2s,opacity .2s,transform .3s var(--cw-ease);border-radius:7px}.cw-entry-icon{display:flex;width:42px;min-width:42px;align-items:center;justify-content:center;transition:transform .3s var(--cw-ease);filter:drop-shadow(0 3px 2px #08192c66)}.cw-entry-icon svg{width:34px;height:34px}.cw-entry-label{min-width:0;display:block}.cw-name{display:block;font-size:18px;font-weight:400;line-height:1.4;overflow-wrap:anywhere;text-shadow:0 2px 3px #07192b66}.cw-row-meta{display:block;font-size:11px;color:var(--cw-muted);margin-top:3px}.cw-row-date{display:none;font-size:11px;color:var(--cw-muted);margin-top:4px}.cw-target{display:block;font-size:11px;color:var(--cw-muted);overflow-wrap:anywhere;margin-top:4px}.cw-row.cw-selected .cw-entry{background:linear-gradient(90deg,#f4f9ff08,#f4f9ff04);transform:translateX(5px)}.cw-row.cw-selected .cw-entry-icon{transform:scale(1.22)}.cw-row.cw-selected .cw-name{color:#fff}.cw-row:hover .cw-entry{background:#eef7ff08}.cw-unavailable{color:var(--cw-muted)}.cw-notice{position:fixed;bottom:90px;left:48px;right:24px;z-index:3;margin:0;background:var(--cw-bottom);border-radius:4px;font-size:12px;color:var(--cw-muted);padding:12px 24px;max-width:52ch}.cw-empty{padding:36px 28px;color:var(--cw-muted);font-size:15px}.cw-detail{min-width:0;align-self:center;padding:20px 0 28px;view-transition-name:cw-detail}.cw-detail-symbol{margin-bottom:28px;filter:drop-shadow(0 12px 12px #04152e44)}.cw-detail-symbol svg{width:110px;height:110px;stroke-width:.8;opacity:.9}.cw-detail h2{font-size:25px;font-weight:300;margin:0 0 7px;overflow-wrap:anywhere;line-height:1.3;letter-spacing:-.015em}.cw-detail>p{font-size:12px;color:var(--cw-muted);margin:0 0 25px}.cw-detail dl{margin:0;font-size:12px}.cw-detail dl>div{display:grid;grid-template-columns:70px minmax(0,1fr);gap:10px;margin-bottom:10px}.cw-detail dt{color:var(--cw-muted)}.cw-detail dd{margin:0;overflow-wrap:anywhere}.cw-open{display:inline-flex;margin-top:24px;min-height:44px;align-items:center;font-size:14px;border-bottom:1px solid #e8f1ff88;gap:12px}.cw-open:hover{border-bottom-color:#fff}.cw-open:after{content:\"\";display:block;width:7px;height:7px;border-top:1px solid currentColor;border-right:1px solid currentColor;transform:rotate(45deg)}\n.cw-bottom{display:flex;align-items:center;justify-content:space-between;gap:24px;padding:22px 48px;position:fixed;bottom:0;left:0;right:0;background:linear-gradient(0deg,var(--cw-bottom),transparent);min-height:82px}.cw-key-help{display:flex;gap:20px;flex-wrap:wrap;font-size:11px;color:var(--cw-muted)}kbd{font:inherit;color:#fff;margin-right:5px}.cw-appearance{display:flex;align-items:center;gap:16px;flex:none}.cw-appearance select{background:#0b193b70;border:1px solid #d7e9ff33;padding:7px 24px 7px 10px;border-radius:4px;max-width:116px;font-size:12px;min-height:36px}.cw-appearance button{display:flex;align-items:center;gap:8px;background:transparent;font-size:12px;min-height:40px;padding:6px 0}.cw-appearance button svg{width:16px;height:16px}.cw-controller{display:flex;align-items:center;gap:8px;font-size:11px;color:var(--cw-muted)}.cw-controller svg{width:22px;height:22px}.cw-noscript{padding:0 48px 24px;color:var(--cw-muted);font-size:13px}\nbody:not([data-cw-enhanced]) .cw-browser{padding-left:0}body:not([data-cw-enhanced]) .cw-stage{display:block;margin-top:20px}body:not([data-cw-enhanced]) .cw-list-scroll{max-height:none;min-height:0}body:not([data-cw-enhanced]) .cw-parent-placeholder{display:none}body:not([data-cw-enhanced]) .cw-bottom{display:none}\n@view-transition{navigation:auto}::view-transition-old(root),::view-transition-new(root){animation:none}::view-transition-old(cw-background),::view-transition-new(cw-background){animation-duration:420ms}::view-transition-old(cw-browser),::view-transition-old(cw-detail){animation:cw-out 180ms ease-in both}::view-transition-new(cw-browser),::view-transition-new(cw-detail){animation:cw-in 420ms var(--cw-ease) both}::view-transition-old(cw-header),::view-transition-new(cw-header){animation-duration:240ms}@keyframes cw-out{to{opacity:0;transform:translateX(-22px);filter:blur(3px)}}@keyframes cw-in{from{opacity:0;transform:translateX(32px);filter:blur(5px)}}:root[data-cw-transitions=false]::view-transition-group(*){animation-duration:0s}:root[data-cw-transitions=false]::view-transition-old(*),:root[data-cw-transitions=false]::view-transition-new(*){animation:none}.cw-category-changing .cw-files{animation:cw-category-in 260ms var(--cw-ease)}@keyframes cw-category-in{from{opacity:.35;transform:translateX(12px)}}\n@media(min-width:1500px){.cw-stage{margin-top:55px}.cw-categories{gap:48px}.cw-category{min-width:112px}.cw-list-scroll{max-height:calc(100svh - 405px)}}\n@media(max-width:1000px){.cw-top{padding:24px 30px;gap:22px}.cw-stage{padding:0 36px;column-gap:35px}.cw-categories{padding-left:44px;gap:20px}.cw-browser{padding-left:44px}.cw-category{min-width:82px}.cw-list-head{flex-wrap:wrap;gap:8px}.cw-search{margin-left:52px;width:calc(100% - 52px)}.cw-list-scroll{max-height:calc(100svh - 390px)}.cw-bottom{padding:20px 30px}.cw-key-help{gap:12px}.cw-detail h2{font-size:21px}.cw-clock{font-size:12px}}\n@media(max-width:680px){.cw-top{padding:22px 20px 12px;flex-wrap:wrap;gap:16px;min-height:104px}.cw-brand{font-size:18px}.cw-brand span{font-size:10px}.cw-clock{order:1;font-size:11px}.cw-location{order:2;width:100%;font-size:12px;gap:8px}.cw-stage{padding:0 16px;margin:12px auto 100px;display:block}.cw-rail{margin:0 -8px 8px;padding:12px 8px 20px}.cw-categories{padding-left:0;gap:14px}.cw-browser{padding-left:0}.cw-category{min-width:76px;min-height:83px;padding:10px 2px;gap:10px}.cw-category svg{width:33px;height:33px}.cw-category span{font-size:12px}.cw-category small{right:5px}.cw-list-head{padding:0 12px;margin-bottom:8px}.cw-directory{gap:12px}.cw-directory h2{font-size:16px}.cw-search{margin-left:46px;width:calc(100% - 46px);min-height:44px}.cw-search input,.cw-search button{min-height:44px}.cw-list-scroll{max-height:none;min-height:150px;padding:8px 2px 18px}.cw-entry{gap:20px;padding:14px 12px;min-height:78px}.cw-entry-icon{width:36px;min-width:36px}.cw-entry-icon svg{width:29px;height:29px}.cw-name{font-size:17px}.cw-detail{display:none!important}.cw-row-date{display:block}.cw-bottom{padding:16px 20px;min-height:76px;gap:12px}.cw-key-help{display:none!important}.cw-appearance{width:100%;justify-content:space-between}.cw-appearance select,.cw-appearance button{min-height:44px}.cw-controller{position:absolute;bottom:72px;right:20px;background:var(--cw-bottom);padding:6px 9px;border-radius:4px;max-width:calc(100% - 40px)}.cw-notice{left:20px;right:20px;bottom:90px}.cw-noscript{padding:0 24px 24px}}\n@media(max-height:520px){.cw-stage{grid-template-columns:minmax(0,1fr)}.cw-detail{display:none!important}.cw-row-date{display:block}.cw-top{flex-wrap:nowrap}.cw-location{order:0;width:auto}.cw-clock{display:none!important}.cw-top{min-height:58px;padding-top:10px;padding-bottom:8px}.cw-stage{margin-top:0;margin-bottom:70px}.cw-rail{margin-bottom:0;padding-bottom:12px}.cw-category{min-height:68px;gap:4px;padding-top:4px}.cw-category svg{width:28px;height:28px}.cw-list-scroll{max-height:calc(100svh - 235px);min-height:80px}.cw-list-head{min-height:36px;margin-bottom:0}.cw-detail-symbol{margin-bottom:10px}.cw-detail-symbol svg{width:60px;height:60px}.cw-detail>p{margin-bottom:12px}.cw-bottom{min-height:60px;padding-block:10px}.cw-list-head{flex-wrap:nowrap}.cw-search{margin-left:0;width:170px}.cw-detail dl>div{margin-bottom:4px}}\n@media(prefers-reduced-motion:reduce){*,*:before,*:after{scroll-behavior:auto!important;animation:none!important;transition:none!important}::view-transition-group(*),::view-transition-old(*),::view-transition-new(*){animation:none!important}.cw-row.cw-selected .cw-entry,.cw-category.cw-active,.cw-category:hover{transform:none}}\n`;\n","import { readFile } from \"node:fs/promises\";\nimport type { ExplorerTheme, FileSystemEntry, ThemeContext } from \"./model.ts\";\nimport { escapeHtml } from \"./theme-default/components.ts\";\nimport { resolveThemeProject, type ThemeProjectOptions } from \"./theme-project.ts\";\nimport { crosswaveIcon, type CrosswaveIcon } from \"./theme-crosswave/icons.ts\";\nimport { crosswaveStyles } from \"./theme-crosswave/styles.ts\";\n\nconst runtime = await readFile(new URL(\"./crosswave-runtime.js\", import.meta.url), \"utf8\");\nexport type CrosswaveColor = \"azure\" | \"violet\" | \"amber\" | \"rose\" | \"jade\" | \"graphite\";\nexport interface CrosswaveThemeOptions {\n readonly color?: CrosswaveColor;\n readonly backgroundMotion?: boolean;\n readonly pageTransitions?: boolean;\n readonly gamepad?: boolean;\n readonly project?: ThemeProjectOptions;\n}\n\nconst categories = [\n [\"all\", \"All files\"],\n [\"folder\", \"Folders\"],\n [\"image\", \"Photos\"],\n [\"audio\", \"Music\"],\n [\"video\", \"Videos\"],\n [\"document\", \"Documents\"],\n [\"other\", \"Other\"],\n] as const;\nconst extensions: Record<string, CrosswaveIcon> = Object.fromEntries([\n ...[\"png\", \"jpg\", \"jpeg\", \"webp\", \"gif\", \"avif\", \"svg\", \"bmp\", \"heic\"].map((x) => [x, \"image\"]),\n ...[\"mp3\", \"wav\", \"ogg\", \"flac\", \"m4a\", \"aac\", \"aiff\", \"opus\"].map((x) => [x, \"audio\"]),\n ...[\"mp4\", \"webm\", \"mov\", \"mkv\", \"avi\", \"m4v\"].map((x) => [x, \"video\"]),\n ...[\"txt\", \"md\", \"pdf\", \"json\", \"csv\", \"html\", \"xml\", \"doc\", \"docx\", \"rtf\", \"yaml\", \"yml\"].map(\n (x) => [x, \"document\"],\n ),\n]) as Record<string, CrosswaveIcon>;\n\nfunction category(entry: FileSystemEntry): CrosswaveIcon {\n if (entry.kind === \"directory\" || entry.symlink?.targetKind === \"directory\") return \"folder\";\n return extensions[entry.name.split(\".\").at(-1)?.toLowerCase() ?? \"\"] ?? \"other\";\n}\nfunction size(bytes: number): string {\n const units = [\"B\", \"KiB\", \"MiB\", \"GiB\", \"TiB\"];\n const index = bytes > 0 ? Math.min(4, Math.floor(Math.log(bytes) / Math.log(1024))) : 0;\n return `${(bytes / 1024 ** index).toLocaleString(\"en-US\", { maximumFractionDigits: index === 0 ? 0 : 1 })} ${units[index]}`;\n}\nfunction status(entry: FileSystemEntry): string {\n if (entry.symlink?.isBroken) return \"Broken link · declared target only\";\n if (entry.symlink?.isOutsideRoot) return \"Outside root · declared target only\";\n if (entry.symlink?.isTargetExcluded) return \"Excluded target · declared target only\";\n if (entry.symlink?.isCycle) return \"Directory link · cycle\";\n if (entry.kind === \"symlink\") return \"Symbolic link\";\n if (entry.kind === \"directory\") return \"Folder\";\n return \"File\";\n}\nfunction row(entry: FileSystemEntry, context: ThemeContext, index: number): string {\n const href = context.hrefFor(entry);\n const type = category(entry);\n const icon = entry.kind === \"symlink\" ? \"link\" : type;\n const label = `${entry.name}${type === \"folder\" ? \"/\" : \"\"}`;\n const unavailable =\n entry.symlink?.isOutsideRoot || entry.symlink?.isTargetExcluded || entry.symlink?.isBroken;\n const bytes = type === \"folder\" ? \"Folder\" : size(entry.metadata.size);\n const instant = new Date(entry.metadata.times.modifiedAt);\n const modified = Number.isNaN(instant.getTime())\n ? \"Unknown\"\n : `${instant.toISOString().slice(0, 16).replace(\"T\", \" \")} UTC`;\n const target =\n entry.symlink === null\n ? \"\"\n : `<span class=\"cw-target\">${escapeHtml(entry.symlink.target)}</span>`;\n const data = `data-cw-entry data-category=\"${type}\" data-icon=\"${icon}\" data-name=\"${escapeHtml(entry.name)}\" data-path=\"${escapeHtml(entry.relativePath)}\" data-size=\"${escapeHtml(bytes)}\" data-modified=\"${modified}\" data-status=\"${escapeHtml(status(entry))}\" data-exits=\"${context.exitsExplorerFor(entry)}\"`;\n const contents = `<span class=\"cw-entry-icon\">${crosswaveIcon(icon)}</span><span class=\"cw-entry-label\"><span class=\"cw-name\">${escapeHtml(label)}</span>${target}<span class=\"cw-row-meta\">${escapeHtml(status(entry))}${type === \"folder\" || unavailable ? \"\" : ` · ${bytes}`}</span><span class=\"cw-row-date\">Modified ${modified}</span></span>`;\n return `<li class=\"cw-row${index === 0 ? \" cw-selected\" : \"\"}\" ${data}>${href === null ? `<span class=\"cw-entry cw-unavailable\">${contents}</span>` : `<a class=\"cw-entry\" href=\"${escapeHtml(href)}\"${context.exitsExplorerFor(entry) ? ' target=\"_blank\" rel=\"noopener\"' : \" data-cw-navigation\"}>${contents}</a>`}</li>`;\n}\n\n/** PSP-inspired media navigation with original wave graphics and ordinary static links. */\nexport function createCrosswaveTheme(options: CrosswaveThemeOptions = {}): ExplorerTheme {\n const color = options.color ?? \"azure\";\n if (![\"azure\", \"violet\", \"amber\", \"rose\", \"jade\", \"graphite\"].includes(color))\n throw new TypeError(\"Unknown Crosswave color\");\n for (const key of [\"backgroundMotion\", \"pageTransitions\", \"gamepad\"] as const) {\n if (options[key] !== undefined && typeof options[key] !== \"boolean\")\n throw new TypeError(`${key} must be a boolean`);\n }\n const project = resolveThemeProject(options.project);\n return {\n name: \"crosswave\",\n searchIndex: false,\n render(context) {\n const { directory } = context;\n const path = directory.current.relativePath || \"/\";\n const parent =\n directory.parent === null ? null : context.hrefForDirectory(directory.parent.relativePath);\n const segments = directory.current.relativePath.split(\"/\").filter(Boolean);\n const breadcrumbs = segments\n .map((segment, i) =>\n i === segments.length - 1\n ? `<span aria-current=\"page\">${escapeHtml(segment)}</span>`\n : `<a data-cw-navigation href=\"${escapeHtml(context.hrefForDirectory(segments.slice(0, i + 1).join(\"/\")))}\">${escapeHtml(segment)}</a>`,\n )\n .join('<span class=\"cw-separator\" aria-hidden=\"true\">/</span>');\n const tabs = categories\n .map(\n ([type, label], i) =>\n `<button role=\"tab\" id=\"cw-tab-${type}\" aria-selected=\"${i === 0}\" aria-controls=\"cw-panel\" tabindex=\"${i === 0 ? 0 : -1}\" data-cw-category=\"${type}\" class=\"cw-category${i === 0 ? \" cw-active\" : \"\"}\">${crosswaveIcon(type)}<span>${label}</span><small data-cw-category-count=\"${type}\"></small></button>`,\n )\n .join(\"\");\n const html = `<!doctype html>\n<html lang=\"en\" data-cw-color=\"${color}\" data-cw-motion=\"${options.backgroundMotion !== false}\" data-cw-transitions=\"${options.pageTransitions !== false}\" data-cw-gamepad=\"${options.gamepad !== false}\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">${context.documentBaseHref === null ? \"\" : `<base href=\"${escapeHtml(context.documentBaseHref)}\">`}<title>${escapeHtml(path)} · ${escapeHtml(project.name)} · Crosswave</title><link rel=\"stylesheet\" href=\"${escapeHtml(context.assetHref(\"crosswave.css\"))}\"><script src=\"${escapeHtml(context.assetHref(\"crosswave.js\"))}\"></script></head>\n<body><a class=\"cw-skip\" href=\"${escapeHtml(context.hrefForDirectory(directory.current.relativePath))}#cw-panel\">Skip to files</a><div class=\"cw-background\" aria-hidden=\"true\"><canvas id=\"cw-wave\"></canvas><svg class=\"cw-fallback-wave\" viewBox=\"0 0 1440 900\" preserveAspectRatio=\"none\"><path d=\"M-80 570C220 300 370 690 760 450S1260 290 1510 510\"/><path d=\"M-80 540C220 640 400 330 820 470S1240 620 1510 360\"/></svg></div>\n<header class=\"cw-top\"><a class=\"cw-brand\" data-cw-navigation href=\"${escapeHtml(context.hrefForDirectory(\"\"))}\">${escapeHtml(project.name)}<span>Crosswave</span></a><nav class=\"cw-location\" aria-label=\"Location\"><a data-cw-navigation href=\"${escapeHtml(context.hrefForDirectory(\"\"))}\">Home</a>${breadcrumbs ? `<span class=\"cw-separator\" aria-hidden=\"true\">/</span>${breadcrumbs}` : \"\"}</nav><time class=\"cw-clock\" aria-label=\"Current time\" hidden></time></header>\n<main class=\"cw-stage\" data-cw-root=\"${escapeHtml(context.hrefForDirectory(\"\"))}\"><h1 class=\"cw-sr\">Files in ${escapeHtml(path)}</h1><nav class=\"cw-rail\" aria-label=\"File categories\" hidden><div class=\"cw-categories\" role=\"tablist\" aria-label=\"File categories\">${tabs}</div></nav>\n<section class=\"cw-browser\" id=\"cw-panel\" aria-label=\"Files\"><div class=\"cw-list-head\"><div class=\"cw-directory\">${parent === null ? '<span class=\"cw-parent-placeholder\"></span>' : `<a class=\"cw-parent\" data-cw-parent data-cw-navigation href=\"${escapeHtml(parent)}\" aria-label=\"Parent directory\">${crosswaveIcon(\"back\")}</a>`}<h2 data-cw-heading>All files</h2><span data-cw-count>${directory.entries.length} items</span></div><div class=\"cw-search\" hidden>${crosswaveIcon(\"search\")}<label class=\"cw-sr\" for=\"cw-search\">Search this folder</label><input id=\"cw-search\" type=\"search\" placeholder=\"Search this folder\" autocomplete=\"off\" spellcheck=\"false\"><button type=\"button\" data-cw-clear hidden>Clear</button></div></div>\n<div class=\"cw-list-scroll\"><ul class=\"cw-files\">${directory.entries.map((entry, i) => row(entry, context, i)).join(\"\")}</ul><p class=\"cw-empty\" data-cw-empty${directory.entries.length ? \" hidden\" : \"\"}>${directory.entries.length ? \"No files match this search.\" : \"This folder is empty.\"}</p></div><p class=\"cw-sr\" role=\"status\" aria-live=\"polite\" data-cw-status></p></section>\n<aside class=\"cw-detail\" aria-label=\"Selected file details\" hidden><div class=\"cw-detail-symbol\" data-cw-detail-icon></div><h2 data-cw-detail-name></h2><p data-cw-detail-status></p><dl><div><dt>Path</dt><dd data-cw-detail-path></dd></div><div><dt>Size</dt><dd data-cw-detail-size></dd></div><div><dt>Modified</dt><dd data-cw-detail-modified></dd></div></dl><a class=\"cw-open\" data-cw-open>Open file</a></aside></main>\n<footer class=\"cw-bottom\"><span class=\"cw-key-help\" hidden><span><kbd>Arrow keys</kbd> Navigate</span><span><kbd>Enter</kbd> Open</span><span><kbd>Backspace</kbd> Parent</span></span><span class=\"cw-controller\" role=\"status\" hidden>${crosswaveIcon(\"controller\")}<span data-cw-controller-label></span></span><div class=\"cw-appearance\" hidden><label class=\"cw-sr\" for=\"cw-color\">Background color</label><select id=\"cw-color\" aria-label=\"Background color\"><option value=\"azure\">Azure</option><option value=\"violet\">Violet</option><option value=\"amber\">Amber</option><option value=\"rose\">Rose</option><option value=\"jade\">Jade</option><option value=\"graphite\">Graphite</option></select><button data-cw-motion aria-pressed=\"false\" type=\"button\">${crosswaveIcon(\"pause\")}<span>Pause waves</span></button></div></footer><noscript><p class=\"cw-noscript\">All files are available below. Categories, search, animated waves, and controller navigation need JavaScript.</p></noscript></body></html>`;\n return { html, assets: { \"crosswave.css\": crosswaveStyles, \"crosswave.js\": runtime } };\n },\n };\n}\n","import type { ExplorerTheme, FileSystemEntry, ThemeContext } from \"./model.ts\";\nimport { resolveThemeProject, type ThemeProjectOptions } from \"./theme-project.ts\";\nimport { utcTimestamp } from \"./timestamp.ts\";\n\nexport interface PlainThemeOptions {\n readonly project?: ThemeProjectOptions;\n}\n\nfunction escapeHtml(value: string): string {\n return value\n .replaceAll(\"&\", \"&\")\n .replaceAll(\"<\", \"<\")\n .replaceAll(\">\", \">\")\n .replaceAll('\"', \""\");\n}\n\nfunction externalHref(value: string | undefined): string | null {\n if (value === undefined) return null;\n try {\n const url = new URL(value);\n return url.protocol === \"https:\" || url.protocol === \"http:\" ? url.href : null;\n } catch {\n return null;\n }\n}\n\nfunction externalLink(label: string, href: string | null): string {\n return href === null\n ? escapeHtml(label)\n : `<a href=\"${escapeHtml(href)}\" target=\"_blank\" rel=\"noopener\">${escapeHtml(label)}</a>`;\n}\n\nfunction status(entry: FileSystemEntry): string {\n if (entry.symlink?.isBroken) return \"broken link\";\n if (entry.symlink?.isOutsideRoot) return \"unavailable link\";\n if (entry.symlink?.isCycle) return \"cycle link\";\n if (entry.kind === \"symlink\") return \"link\";\n if (entry.kind === \"directory\") return \"folder\";\n return \"file\";\n}\n\nfunction renderBreadcrumbs(context: ThemeContext): string {\n const segments = context.directory.current.relativePath.split(\"/\").filter(Boolean);\n if (segments.length === 0) return \"\";\n const ancestors = segments.slice(0, -1).map((segment, index) => {\n const relativePath = segments.slice(0, index + 1).join(\"/\");\n return `<a href=\"${escapeHtml(context.hrefForDirectory(relativePath))}\">${escapeHtml(segment)}</a>`;\n });\n return `<nav aria-label=\"Breadcrumb\"><a href=\"${escapeHtml(context.hrefForDirectory(\"\"))}\">Home</a> / ${ancestors.length === 0 ? \"\" : `${ancestors.join(\" / \")} / `}<span aria-current=\"page\">${escapeHtml(segments.at(-1) ?? \"\")}</span></nav>`;\n}\n\nfunction renderEntry(entry: FileSystemEntry, context: ThemeContext): string {\n const directoryLike = entry.kind === \"directory\" || entry.symlink?.targetKind === \"directory\";\n const name = `${escapeHtml(entry.name)}${directoryLike ? \"/\" : \"\"}`;\n const href = context.hrefFor(entry);\n const exitsExplorer = context.exitsExplorerFor(entry);\n const link =\n href === null\n ? `<span>${name}</span>`\n : `<a href=\"${escapeHtml(href)}\"${exitsExplorer ? ' target=\"_blank\" rel=\"noopener\"' : \"\"}>${name}</a>`;\n const details = [status(entry)];\n if (!directoryLike) details.push(`${entry.metadata.size} B`);\n if (entry.symlink !== null) details.push(`Target: ${entry.symlink.target}`);\n if (exitsExplorer) details.push(\"opens in a new tab\");\n const modified = utcTimestamp(entry.metadata.times.modifiedAt);\n return `<li>${link}<small>${escapeHtml(details.join(\" · \"))} · ${modified === null ? \"Unknown\" : `<time datetime=\"${modified.datetime}\">${modified.label}</time>`}</small></li>`;\n}\n\n/** A no-icon, no-script theme that leaves navigation and controls to the browser. */\nexport function createPlainTheme(options: PlainThemeOptions = {}): ExplorerTheme {\n const project = resolveThemeProject(options.project);\n const repositoryHref = externalHref(project.repositoryUrl);\n const licenseHref = externalHref(\n options.project?.licenseUrl ??\n (options.project?.license === undefined ? \"https://opensource.org/license/mit\" : undefined),\n );\n return {\n name: \"plain\",\n searchIndex: false,\n render(context) {\n const visiblePath =\n context.directory.current.relativePath === \"\"\n ? \"/\"\n : `/${context.directory.current.relativePath}/`;\n const parentHref =\n context.directory.parent === null\n ? null\n : context.hrefForDirectory(context.directory.parent.relativePath);\n const parentRow =\n parentHref === null ? \"\" : `<li><a href=\"${escapeHtml(parentHref)}\">../</a></li>`;\n const rows = context.directory.entries.map((entry) => renderEntry(entry, context)).join(\"\");\n const count = context.directory.entries.length;\n const footer = `<footer><hr><p>Repository: ${externalLink(project.name, repositoryHref)} by ${escapeHtml(project.author)} · ${externalLink(project.license, licenseHref)}</p></footer>`;\n const html = `<!doctype html>\n<html lang=\"en\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"><meta name=\"color-scheme\" content=\"light\">${context.documentBaseHref === null ? \"\" : `<base href=\"${escapeHtml(context.documentBaseHref)}\">`}<title>Index of ${escapeHtml(visiblePath)}</title><style>body{max-width:72ch;margin:2rem auto;padding:0 1rem}h1,li,nav{overflow-wrap:anywhere}li{margin:.6rem 0}small{display:block}</style></head>\n<body><main>${renderBreadcrumbs(context)}<h1>Index of ${escapeHtml(visiblePath)}</h1><p>${count} ${count === 1 ? \"entry\" : \"entries\"}</p>${count === 0 ? \"<p>This directory is empty.</p>\" : \"\"}<ul>${parentRow}${rows}</ul></main>${footer}</body></html>`;\n return { html };\n },\n };\n}\n"],"mappings":"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"}
|