@vizejs/vite-plugin-musea 0.101.0 → 0.103.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,483 @@
1
+ import "node:module";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { randomBytes, timingSafeEqual } from "node:crypto";
5
+ //#region \0rolldown/runtime.js
6
+ var __defProp = Object.defineProperty;
7
+ var __exportAll = (all, no_symbols) => {
8
+ let target = {};
9
+ for (var name in all) __defProp(target, name, {
10
+ get: all[name],
11
+ enumerable: true
12
+ });
13
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
14
+ return target;
15
+ };
16
+ //#endregion
17
+ //#region src/security.ts
18
+ const DEFAULT_API_BODY_LIMIT_BYTES = 1024 * 1024;
19
+ var HttpError = class extends Error {
20
+ status;
21
+ constructor(message, status) {
22
+ super(message);
23
+ this.name = "HttpError";
24
+ this.status = status;
25
+ }
26
+ };
27
+ function createDevSessionToken() {
28
+ return randomBytes(32).toString("base64url");
29
+ }
30
+ function realpathNearest(targetPath) {
31
+ let current = path.resolve(targetPath);
32
+ const missingParts = [];
33
+ while (true) try {
34
+ const real = fs.realpathSync.native(current);
35
+ return missingParts.length > 0 ? path.join(real, ...missingParts.reverse()) : real;
36
+ } catch {
37
+ const parent = path.dirname(current);
38
+ if (parent === current) return path.resolve(targetPath);
39
+ missingParts.push(path.basename(current));
40
+ current = parent;
41
+ }
42
+ }
43
+ function isResolvedPathInside(parentDir, candidatePath) {
44
+ const parent = path.resolve(parentDir);
45
+ const candidate = path.resolve(candidatePath);
46
+ const relative = path.relative(parent, candidate);
47
+ return relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative);
48
+ }
49
+ function isPathInsideAny(parentDirs, candidatePath) {
50
+ const candidate = realpathNearest(candidatePath);
51
+ return parentDirs.some((parentDir) => isResolvedPathInside(realpathNearest(parentDir), candidate));
52
+ }
53
+ function resolveInside(parentDir, candidatePath, label = "path") {
54
+ return resolveInsideAny([parentDir], candidatePath, label);
55
+ }
56
+ function resolveInsideAny(parentDirs, candidatePath, label = "path") {
57
+ if (candidatePath.includes("\0")) throw new HttpError(`${label} contains an invalid character`, 400);
58
+ if (parentDirs.length === 0) throw new HttpError(`No allowed directories configured for ${label}`, 500);
59
+ const parent = path.resolve(parentDirs[0] ?? ".");
60
+ const resolved = path.isAbsolute(candidatePath) ? path.resolve(candidatePath) : path.resolve(parent, candidatePath);
61
+ if (!isPathInsideAny(parentDirs, resolved)) throw new HttpError(`${label} escapes the allowed directory`, 400);
62
+ return resolved;
63
+ }
64
+ function resolveUrlPathInside(parentDir, requestUrl, label = "path") {
65
+ let pathname = decodeUrlComponent(requestUrl.split(/[?#]/, 1)[0] || "/", label);
66
+ pathname = pathname.replaceAll("\\", "/");
67
+ if (pathname.split("/").includes("..")) throw new HttpError(`${label} must not contain parent directory segments`, 400);
68
+ return resolveInside(parentDir, `.${pathname}`, label);
69
+ }
70
+ function decodeUrlComponent(value, label = "path") {
71
+ try {
72
+ return decodeURIComponent(value);
73
+ } catch {
74
+ throw new HttpError(`${label} is not valid URL encoding`, 400);
75
+ }
76
+ }
77
+ function collectRequestBody(req, limit = DEFAULT_API_BODY_LIMIT_BYTES) {
78
+ return new Promise((resolve, reject) => {
79
+ let body = "";
80
+ let size = 0;
81
+ let completed = false;
82
+ req.on("data", (chunk) => {
83
+ if (completed) return;
84
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
85
+ size += buffer.byteLength;
86
+ if (size > limit) {
87
+ completed = true;
88
+ reject(new HttpError(`Request body exceeds ${limit} bytes`, 413));
89
+ return;
90
+ }
91
+ body += buffer.toString("utf-8");
92
+ });
93
+ req.on("end", () => {
94
+ if (!completed) {
95
+ completed = true;
96
+ resolve(body);
97
+ }
98
+ });
99
+ req.on("error", (error) => {
100
+ if (!completed) {
101
+ completed = true;
102
+ reject(error);
103
+ }
104
+ });
105
+ });
106
+ }
107
+ function validateDevApiRequest(req, sessionToken) {
108
+ const originError = validateOrigin(req);
109
+ if (originError) return originError;
110
+ if (!isUnsafeMethod(req.method)) return null;
111
+ if (!hasValidSessionToken(req, sessionToken)) return new HttpError("Invalid Musea dev session token", 403);
112
+ if (!isJsonRequest(req)) return new HttpError("Content-Type must be application/json", 415);
113
+ return null;
114
+ }
115
+ function serializeScriptValue(value) {
116
+ return (JSON.stringify(value) ?? "undefined").replace(/[<>&\u2028\u2029]/g, (char) => {
117
+ switch (char) {
118
+ case "<": return "\\u003C";
119
+ case ">": return "\\u003E";
120
+ case "&": return "\\u0026";
121
+ case "\u2028": return "\\u2028";
122
+ case "\u2029": return "\\u2029";
123
+ default: return char;
124
+ }
125
+ });
126
+ }
127
+ function isUnsafeMethod(method) {
128
+ return method === "POST" || method === "PUT" || method === "PATCH" || method === "DELETE";
129
+ }
130
+ function isJsonRequest(req) {
131
+ return getHeader(req, "content-type")?.split(";")[0]?.trim().toLowerCase() === "application/json";
132
+ }
133
+ function validateOrigin(req) {
134
+ if (getHeader(req, "sec-fetch-site") === "cross-site") return new HttpError("Cross-origin Musea API requests are not allowed", 403);
135
+ const origin = getHeader(req, "origin");
136
+ if (!origin) return null;
137
+ const host = getHeader(req, "host");
138
+ if (!host) return new HttpError("Missing Host header", 400);
139
+ try {
140
+ if (new URL(origin).host !== host) return new HttpError("Cross-origin Musea API requests are not allowed", 403);
141
+ } catch {
142
+ return new HttpError("Invalid Origin header", 400);
143
+ }
144
+ return null;
145
+ }
146
+ function hasValidSessionToken(req, expectedToken) {
147
+ const actualToken = getHeader(req, "x-musea-session");
148
+ if (!actualToken) return false;
149
+ const actual = Buffer.from(actualToken);
150
+ const expected = Buffer.from(expectedToken);
151
+ return actual.length === expected.length && timingSafeEqual(actual, expected);
152
+ }
153
+ function getHeader(req, name) {
154
+ const value = req.headers[name];
155
+ if (Array.isArray(value)) return value[0];
156
+ return value;
157
+ }
158
+ //#endregion
159
+ //#region src/gallery/styles-base.css?inline
160
+ var styles_base_default = ":root {\n --musea-bg-primary: #e6e2d6;\n --musea-bg-secondary: #ddd9cd;\n --musea-bg-tertiary: #d4d0c4;\n --musea-bg-elevated: #e6e2d6;\n --musea-accent: #121212;\n --musea-accent-hover: #2a2a2a;\n --musea-accent-subtle: #12121214;\n --musea-text: #121212;\n --musea-text-secondary: #3a3a3a;\n --musea-text-muted: #6b6b6b;\n --musea-border: #c8c4b8;\n --musea-border-subtle: #d4d0c4;\n --musea-success: #16a34a;\n --musea-shadow: 0 4px 24px #00000014;\n --musea-radius-sm: 4px;\n --musea-radius-md: 6px;\n --musea-radius-lg: 8px;\n --musea-transition: .15s ease;\n}\n\n* {\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n}\n\nbody {\n background: var(--musea-bg-primary);\n color: var(--musea-text);\n -webkit-font-smoothing: antialiased;\n min-height: 100vh;\n font-family: Helvetica Neue, Helvetica, Arial, sans-serif;\n line-height: 1.5;\n}\n";
161
+ //#endregion
162
+ //#region src/gallery/styles-layout.css?inline
163
+ var styles_layout_default = ".header {\n background: var(--musea-bg-secondary);\n border-bottom: 1px solid var(--musea-border);\n z-index: 100;\n justify-content: space-between;\n align-items: center;\n height: 56px;\n padding: 0 1.5rem;\n display: flex;\n position: sticky;\n top: 0;\n}\n\n.header-left {\n align-items: center;\n gap: 1.5rem;\n display: flex;\n}\n\n.logo {\n color: var(--musea-accent);\n align-items: center;\n gap: .5rem;\n font-size: 1.125rem;\n font-weight: 700;\n text-decoration: none;\n display: flex;\n}\n\n.logo-svg {\n flex-shrink: 0;\n width: 32px;\n height: 32px;\n}\n\n.logo-icon svg {\n width: 16px;\n height: 16px;\n color: var(--musea-text);\n}\n\n.header-subtitle {\n color: var(--musea-text-muted);\n border-left: 1px solid var(--musea-border);\n padding-left: 1.5rem;\n font-size: .8125rem;\n font-weight: 500;\n}\n\n.search-container {\n width: 280px;\n position: relative;\n}\n\n.search-input {\n background: var(--musea-bg-tertiary);\n border: 1px solid var(--musea-border);\n border-radius: var(--musea-radius-md);\n width: 100%;\n color: var(--musea-text);\n transition: border-color var(--musea-transition),\n background var(--musea-transition);\n outline: none;\n padding: .5rem .75rem .5rem 2.25rem;\n font-size: .8125rem;\n}\n\n.search-input::placeholder {\n color: var(--musea-text-muted);\n}\n\n.search-input:focus {\n border-color: var(--musea-accent);\n background: var(--musea-bg-elevated);\n}\n\n.search-icon {\n color: var(--musea-text-muted);\n pointer-events: none;\n position: absolute;\n top: 50%;\n left: .75rem;\n transform: translateY(-50%);\n}\n\n.main {\n grid-template-columns: 260px 1fr;\n min-height: calc(100vh - 56px);\n display: grid;\n}\n\n.sidebar {\n background: var(--musea-bg-secondary);\n border-right: 1px solid var(--musea-border);\n overflow: hidden auto;\n}\n\n.sidebar::-webkit-scrollbar {\n width: 6px;\n}\n\n.sidebar::-webkit-scrollbar-track {\n background: none;\n}\n\n.sidebar::-webkit-scrollbar-thumb {\n background: var(--musea-border);\n border-radius: 3px;\n}\n\n.sidebar-section {\n padding: .75rem;\n}\n\n.category-header {\n text-transform: uppercase;\n letter-spacing: .08em;\n color: var(--musea-text-muted);\n cursor: pointer;\n user-select: none;\n border-radius: var(--musea-radius-sm);\n transition: background var(--musea-transition);\n align-items: center;\n gap: .5rem;\n padding: .625rem .75rem;\n font-size: .6875rem;\n font-weight: 600;\n display: flex;\n}\n\n.category-header:hover {\n background: var(--musea-bg-tertiary);\n}\n\n.category-icon {\n width: 16px;\n height: 16px;\n transition: transform var(--musea-transition);\n}\n\n.category-header.collapsed .category-icon {\n transform: rotate(-90deg);\n}\n\n.category-count {\n background: var(--musea-bg-tertiary);\n border-radius: 4px;\n margin-left: auto;\n padding: .125rem .375rem;\n font-size: .625rem;\n}\n\n.art-list {\n margin-top: .25rem;\n list-style: none;\n}\n\n.art-item {\n border-radius: var(--musea-radius-sm);\n cursor: pointer;\n color: var(--musea-text-secondary);\n transition: all var(--musea-transition);\n align-items: center;\n gap: .625rem;\n padding: .5rem .75rem .5rem 1.75rem;\n font-size: .8125rem;\n display: flex;\n position: relative;\n}\n\n.art-item:before {\n content: \"\";\n background: var(--musea-border);\n width: 6px;\n height: 6px;\n transition: background var(--musea-transition);\n border-radius: 50%;\n position: absolute;\n top: 50%;\n left: .75rem;\n transform: translateY(-50%);\n}\n\n.art-item:hover {\n background: var(--musea-bg-tertiary);\n color: var(--musea-text);\n}\n\n.art-item:hover:before {\n background: var(--musea-text-muted);\n}\n\n.art-item.active {\n background: var(--musea-accent-subtle);\n color: var(--musea-accent-hover);\n}\n\n.art-item.active:before {\n background: var(--musea-accent);\n}\n\n.art-variant-count {\n color: var(--musea-text-muted);\n opacity: 0;\n transition: opacity var(--musea-transition);\n margin-left: auto;\n font-size: .6875rem;\n}\n\n.art-item:hover .art-variant-count {\n opacity: 1;\n}\n\n.content {\n background: var(--musea-bg-primary);\n overflow-y: auto;\n}\n\n.content-inner {\n max-width: 1400px;\n margin: 0 auto;\n padding: 2rem;\n}\n\n.content-header {\n margin-bottom: 2rem;\n}\n\n.content-title {\n margin-bottom: .5rem;\n font-size: 1.5rem;\n font-weight: 700;\n}\n\n.content-description {\n color: var(--musea-text-muted);\n max-width: 600px;\n font-size: .9375rem;\n}\n\n.content-meta {\n align-items: center;\n gap: 1rem;\n margin-top: 1rem;\n display: flex;\n}\n\n.meta-tag {\n background: var(--musea-bg-secondary);\n border: 1px solid var(--musea-border);\n border-radius: var(--musea-radius-sm);\n color: var(--musea-text-muted);\n align-items: center;\n gap: .375rem;\n padding: .25rem .625rem;\n font-size: .75rem;\n display: inline-flex;\n}\n\n.meta-tag svg {\n width: 12px;\n height: 12px;\n}\n";
164
+ //#endregion
165
+ //#region src/gallery/styles-components.css?inline
166
+ var styles_components_default = ".gallery {\n grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));\n gap: 1.25rem;\n display: grid;\n}\n\n.variant-card {\n background: var(--musea-bg-secondary);\n border: 1px solid var(--musea-border);\n border-radius: var(--musea-radius-lg);\n overflow: hidden;\n}\n\n.variant-preview {\n aspect-ratio: 16 / 7;\n background: var(--musea-bg-tertiary);\n box-sizing: border-box;\n justify-content: center;\n align-items: center;\n display: flex;\n position: relative;\n overflow: hidden;\n}\n\n.variant-preview iframe {\n border-radius: var(--musea-radius-md);\n background: #fff;\n border: none;\n width: 70%;\n max-width: 100%;\n height: 100%;\n max-height: 100%;\n box-shadow: 0 12px 30px #0d0d0d1f;\n}\n\n.variant-preview-placeholder {\n color: var(--musea-text-muted);\n text-align: center;\n padding: 1rem;\n font-size: .8125rem;\n}\n\n.variant-preview-code {\n color: var(--musea-text-muted);\n background: var(--musea-bg-primary);\n width: 100%;\n max-height: 100%;\n padding: 1rem;\n font-family: JetBrains Mono, SF Mono, Fira Code, monospace;\n font-size: .75rem;\n overflow: auto;\n}\n\n.variant-info {\n border-top: 1px solid var(--musea-border);\n justify-content: space-between;\n align-items: center;\n padding: 1rem;\n display: flex;\n}\n\n.variant-name {\n font-size: .875rem;\n font-weight: 600;\n}\n\n.variant-badge {\n text-transform: uppercase;\n letter-spacing: .04em;\n background: var(--musea-accent-subtle);\n color: var(--musea-accent);\n border-radius: 4px;\n padding: .1875rem .5rem;\n font-size: .625rem;\n font-weight: 600;\n}\n\n.variant-actions {\n gap: .5rem;\n display: flex;\n}\n\n.variant-action-btn {\n background: var(--musea-bg-tertiary);\n border-radius: var(--musea-radius-sm);\n width: 28px;\n height: 28px;\n color: var(--musea-text-muted);\n cursor: pointer;\n transition: all var(--musea-transition);\n border: none;\n justify-content: center;\n align-items: center;\n display: flex;\n}\n\n.variant-action-btn:hover {\n background: var(--musea-bg-elevated);\n color: var(--musea-text);\n}\n\n.variant-action-btn svg {\n width: 14px;\n height: 14px;\n}\n\n.empty-state {\n text-align: center;\n flex-direction: column;\n justify-content: center;\n align-items: center;\n min-height: 400px;\n padding: 2rem;\n display: flex;\n}\n\n.empty-state-icon {\n background: var(--musea-bg-secondary);\n border-radius: var(--musea-radius-lg);\n justify-content: center;\n align-items: center;\n width: 80px;\n height: 80px;\n margin-bottom: 1.5rem;\n display: flex;\n}\n\n.empty-state-icon svg {\n width: 40px;\n height: 40px;\n color: var(--musea-text-muted);\n}\n\n.empty-state-title {\n margin-bottom: .5rem;\n font-size: 1.125rem;\n font-weight: 600;\n}\n\n.empty-state-text {\n color: var(--musea-text-muted);\n max-width: 300px;\n font-size: .875rem;\n}\n\n.loading {\n min-height: 200px;\n color: var(--musea-text-muted);\n justify-content: center;\n align-items: center;\n gap: .75rem;\n display: flex;\n}\n\n.loading-spinner {\n border: 2px solid var(--musea-border);\n border-top-color: var(--musea-accent);\n border-radius: 50%;\n width: 20px;\n height: 20px;\n animation: .8s linear infinite spin;\n}\n\n@keyframes spin {\n to {\n transform: rotate(360deg);\n }\n}\n\n@media (width <= 768px) {\n .main {\n grid-template-columns: 1fr;\n }\n\n .sidebar, .header-subtitle {\n display: none;\n }\n}\n";
167
+ //#endregion
168
+ //#region src/gallery/styles.ts
169
+ /**
170
+ * CSS theme variables and style definitions for the Musea gallery.
171
+ *
172
+ * CSS is split into separate .css files and imported as text
173
+ * via tsdown's `?inline` support.
174
+ */
175
+ /**
176
+ * Generate the full gallery CSS styles string.
177
+ */
178
+ function generateGalleryStyles() {
179
+ return `${styles_base_default}\n${styles_layout_default}\n${styles_components_default}`;
180
+ }
181
+ //#endregion
182
+ //#region src/gallery/template.ts
183
+ /**
184
+ * HTML structure and inline JS generation for the Musea gallery.
185
+ *
186
+ * Extracted from gallery.ts to keep file sizes manageable.
187
+ */
188
+ function escapeHtmlAttribute(value) {
189
+ return value.replace(/[&<>"']/g, (char) => {
190
+ switch (char) {
191
+ case "&": return "&amp;";
192
+ case "<": return "&lt;";
193
+ case ">": return "&gt;";
194
+ case "\"": return "&quot;";
195
+ case "'": return "&#39;";
196
+ default: return char;
197
+ }
198
+ });
199
+ }
200
+ /**
201
+ * Generate the gallery HTML body (header, sidebar, content, and inline script).
202
+ */
203
+ function generateGalleryBody(basePath) {
204
+ return `
205
+ <header class="header">
206
+ <div class="header-left">
207
+ <a href="${escapeHtmlAttribute(basePath)}" class="logo">
208
+ <svg class="logo-svg" width="32" height="32" viewBox="0 0 200 200" fill="none">
209
+ <g transform="translate(30, 25) scale(1.2)">
210
+ <g transform="translate(15, 10) skewX(-15)">
211
+ <path d="M 65 0 L 40 60 L 70 20 L 65 0 Z" fill="currentColor"/>
212
+ <path d="M 20 0 L 40 60 L 53 13 L 20 0 Z" fill="currentColor"/>
213
+ </g>
214
+ </g>
215
+ <g transform="translate(110, 120)">
216
+ <line x1="5" y1="10" x2="5" y2="50" stroke="currentColor" stroke-width="3" stroke-linecap="round"/>
217
+ <line x1="60" y1="10" x2="60" y2="50" stroke="currentColor" stroke-width="3" stroke-linecap="round"/>
218
+ <path d="M 0 10 L 32.5 0 L 65 10" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
219
+ <rect x="15" y="18" width="14" height="12" rx="1" fill="none" stroke="currentColor" stroke-width="1.5" opacity="0.7"/>
220
+ <rect x="36" y="18" width="14" height="12" rx="1" fill="none" stroke="currentColor" stroke-width="1.5" opacity="0.7"/>
221
+ <rect x="23" y="35" width="18" height="12" rx="1" fill="none" stroke="currentColor" stroke-width="1.5" opacity="0.6"/>
222
+ </g>
223
+ </svg>
224
+ Musea
225
+ </a>
226
+ <span class="header-subtitle">Component Gallery</span>
227
+ </div>
228
+ <div class="search-container">
229
+ <svg class="search-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
230
+ <circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/>
231
+ </svg>
232
+ <input type="text" class="search-input" placeholder="Search components..." id="search">
233
+ </div>
234
+ </header>
235
+
236
+ <main class="main">
237
+ <aside class="sidebar" id="sidebar">
238
+ <div class="loading">
239
+ <div class="loading-spinner"></div>
240
+ Loading...
241
+ </div>
242
+ </aside>
243
+ <section class="content" id="content">
244
+ <div class="empty-state">
245
+ <div class="empty-state-icon">
246
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
247
+ <path d="M4 5a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V5Z"/>
248
+ <path d="M4 13a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1v-6Z"/>
249
+ <path d="M16 13a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1v-6Z"/>
250
+ </svg>
251
+ </div>
252
+ <div class="empty-state-title">Select a component</div>
253
+ <div class="empty-state-text">Choose a component from the sidebar to view its variants and documentation</div>
254
+ </div>
255
+ </section>
256
+ </main>`;
257
+ }
258
+ /**
259
+ * Generate the gallery inline script (SPA logic).
260
+ */
261
+ function generateGalleryScript(basePath) {
262
+ return `
263
+ const basePath = ${serializeScriptValue(basePath)};
264
+ let arts = [];
265
+ let selectedArt = null;
266
+ let searchQuery = '';
267
+
268
+ async function loadArts() {
269
+ try {
270
+ const res = await fetch(basePath + '/api/arts');
271
+ arts = await res.json();
272
+ renderSidebar();
273
+ } catch (e) {
274
+ console.error('Failed to load arts:', e);
275
+ document.getElementById('sidebar').innerHTML = '<div class="loading">Failed to load</div>';
276
+ }
277
+ }
278
+
279
+ function renderSidebar() {
280
+ const sidebar = document.getElementById('sidebar');
281
+ const categories = {};
282
+
283
+ const filtered = searchQuery
284
+ ? arts.filter(a => a.metadata.title.toLowerCase().includes(searchQuery.toLowerCase()))
285
+ : arts;
286
+
287
+ for (const art of filtered) {
288
+ const cat = art.metadata.category || 'Components';
289
+ if (!categories[cat]) categories[cat] = [];
290
+ categories[cat].push(art);
291
+ }
292
+
293
+ if (Object.keys(categories).length === 0) {
294
+ sidebar.innerHTML = '<div class="sidebar-section"><div class="loading">No components found</div></div>';
295
+ return;
296
+ }
297
+
298
+ let html = '';
299
+ for (const [category, items] of Object.entries(categories)) {
300
+ const escapedCategory = escapeHtml(category);
301
+ html += '<div class="sidebar-section">';
302
+ html += '<div class="category-header" data-category="' + escapedCategory + '">';
303
+ html += '<svg class="category-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="m9 18 6-6-6-6"/></svg>';
304
+ html += '<span>' + escapedCategory + '</span>';
305
+ html += '<span class="category-count">' + items.length + '</span>';
306
+ html += '</div>';
307
+ html += '<ul class="art-list" data-category="' + escapedCategory + '">';
308
+ for (const art of items) {
309
+ const active = selectedArt?.path === art.path ? 'active' : '';
310
+ const variantCount = art.variants?.length || 0;
311
+ html += '<li class="art-item ' + active + '" data-path="' + escapeHtml(art.path) + '">';
312
+ html += '<span>' + escapeHtml(art.metadata.title) + '</span>';
313
+ html += '<span class="art-variant-count">' + variantCount + ' variant' + (variantCount !== 1 ? 's' : '') + '</span>';
314
+ html += '</li>';
315
+ }
316
+ html += '</ul>';
317
+ html += '</div>';
318
+ }
319
+
320
+ sidebar.innerHTML = html;
321
+
322
+ sidebar.querySelectorAll('.art-item').forEach(item => {
323
+ item.addEventListener('click', () => {
324
+ const artPath = item.dataset.path;
325
+ selectedArt = arts.find(a => a.path === artPath);
326
+ renderSidebar();
327
+ renderContent();
328
+ });
329
+ });
330
+
331
+ sidebar.querySelectorAll('.category-header').forEach(header => {
332
+ header.addEventListener('click', () => {
333
+ header.classList.toggle('collapsed');
334
+ const list = header.parentElement?.querySelector('.art-list');
335
+ if (list) list.style.display = header.classList.contains('collapsed') ? 'none' : 'block';
336
+ });
337
+ });
338
+ }
339
+
340
+ function renderContent() {
341
+ const content = document.getElementById('content');
342
+ if (!selectedArt) {
343
+ content.innerHTML = \`
344
+ <div class="empty-state">
345
+ <div class="empty-state-icon">
346
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
347
+ <path d="M4 5a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V5Z"/>
348
+ <path d="M4 13a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1v-6Z"/>
349
+ <path d="M16 13a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1v-6Z"/>
350
+ </svg>
351
+ </div>
352
+ <div class="empty-state-title">Select a component</div>
353
+ <div class="empty-state-text">Choose a component from the sidebar to view its variants</div>
354
+ </div>
355
+ \`;
356
+ return;
357
+ }
358
+
359
+ const meta = selectedArt.metadata;
360
+ const tags = meta.tags || [];
361
+ const variantCount = selectedArt.variants?.length || 0;
362
+
363
+ let html = '<div class="content-inner">';
364
+ html += '<div class="content-header">';
365
+ html += '<h1 class="content-title">' + escapeHtml(meta.title) + '</h1>';
366
+ if (meta.description) {
367
+ html += '<p class="content-description">' + escapeHtml(meta.description) + '</p>';
368
+ }
369
+ html += '<div class="content-meta">';
370
+ html += '<span class="meta-tag"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/></svg>' + variantCount + ' variant' + (variantCount !== 1 ? 's' : '') + '</span>';
371
+ if (meta.category) {
372
+ html += '<span class="meta-tag"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>' + escapeHtml(meta.category) + '</span>';
373
+ }
374
+ for (const tag of tags) {
375
+ html += '<span class="meta-tag">#' + escapeHtml(tag) + '</span>';
376
+ }
377
+ html += '</div>';
378
+ html += '</div>';
379
+
380
+ html += '<div class="gallery">';
381
+ for (const variant of selectedArt.variants) {
382
+ const previewUrl = basePath + '/preview?art=' + encodeURIComponent(selectedArt.path) + '&variant=' + encodeURIComponent(variant.name);
383
+ const escapedPreviewUrl = escapeHtml(previewUrl);
384
+
385
+ html += '<div class="variant-card">';
386
+ html += '<div class="variant-preview">';
387
+ html += '<iframe src="' + escapedPreviewUrl + '" loading="lazy" title="' + escapeHtml(variant.name) + '"></iframe>';
388
+ html += '</div>';
389
+ html += '<div class="variant-info">';
390
+ html += '<div>';
391
+ html += '<span class="variant-name">' + escapeHtml(variant.name) + '</span>';
392
+ if (variant.isDefault) html += ' <span class="variant-badge">Default</span>';
393
+ html += '</div>';
394
+ html += '<div class="variant-actions">';
395
+ html += '<button class="variant-action-btn" title="Open in new tab" data-preview-url="' + escapedPreviewUrl + '"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg></button>';
396
+ html += '</div>';
397
+ html += '</div>';
398
+ html += '</div>';
399
+ }
400
+ html += '</div>';
401
+ html += '</div>';
402
+
403
+ content.innerHTML = html;
404
+
405
+ content.querySelectorAll('.variant-action-btn[data-preview-url]').forEach(button => {
406
+ button.addEventListener('click', () => {
407
+ const previewUrl = button.dataset.previewUrl;
408
+ if (previewUrl) window.open(previewUrl, '_blank', 'noopener');
409
+ });
410
+ });
411
+ }
412
+
413
+ function escapeHtml(str) {
414
+ if (!str) return '';
415
+ return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;');
416
+ }
417
+
418
+ // Search
419
+ document.getElementById('search').addEventListener('input', (e) => {
420
+ searchQuery = e.target.value;
421
+ renderSidebar();
422
+ });
423
+
424
+ // Keyboard shortcut for search
425
+ document.addEventListener('keydown', (e) => {
426
+ if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
427
+ e.preventDefault();
428
+ document.getElementById('search').focus();
429
+ }
430
+ });
431
+
432
+ loadArts();`;
433
+ }
434
+ //#endregion
435
+ //#region src/gallery/index.ts
436
+ /**
437
+ * Gallery HTML generation for the Musea component gallery.
438
+ *
439
+ * Contains the inline gallery SPA template (used as a fallback when the
440
+ * pre-built gallery is not available) and the gallery virtual module.
441
+ */
442
+ var gallery_exports = /* @__PURE__ */ __exportAll({
443
+ generateGalleryHtml: () => generateGalleryHtml,
444
+ generateGalleryModule: () => generateGalleryModule
445
+ });
446
+ /**
447
+ * Generate the inline gallery HTML page.
448
+ */
449
+ function generateGalleryHtml(basePath, devSessionToken, themeConfig) {
450
+ const themeScript = themeConfig ? `window.__MUSEA_THEME_CONFIG__=${serializeScriptValue(themeConfig)};` : "";
451
+ return `<!DOCTYPE html>
452
+ <html lang="en">
453
+ <head>
454
+ <meta charset="UTF-8">
455
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
456
+ <title>Musea - Component Gallery</title>
457
+ <script>window.__MUSEA_BASE_PATH__=${serializeScriptValue(basePath)};window.__MUSEA_SESSION_TOKEN__=${serializeScriptValue(devSessionToken)};${themeScript}<\/script>
458
+ <style>${generateGalleryStyles()}
459
+ </style>
460
+ </head>
461
+ <body>${generateGalleryBody(basePath)}
462
+
463
+ <script type="module">${generateGalleryScript(basePath)}
464
+ <\/script>
465
+ </body>
466
+ </html>`;
467
+ }
468
+ /**
469
+ * Generate the virtual gallery module code.
470
+ */
471
+ function generateGalleryModule(basePath) {
472
+ return `
473
+ export const basePath = ${serializeScriptValue(basePath)};
474
+ export async function loadArts() {
475
+ const res = await fetch(basePath + '/api/arts');
476
+ return res.json();
477
+ }
478
+ `;
479
+ }
480
+ //#endregion
481
+ export { collectRequestBody as a, resolveInside as c, serializeScriptValue as d, validateDevApiRequest as f, HttpError as i, resolveInsideAny as l, generateGalleryModule as n, createDevSessionToken as o, DEFAULT_API_BODY_LIMIT_BYTES as r, decodeUrlComponent as s, gallery_exports as t, resolveUrlPathInside as u };
482
+
483
+ //# sourceMappingURL=gallery-zu8hc8Lc.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gallery-zu8hc8Lc.mjs","names":["stylesBase","stylesLayout","stylesComponents"],"sources":["../src/security.ts","../src/gallery/styles-base.css?inline","../src/gallery/styles-layout.css?inline","../src/gallery/styles-components.css?inline","../src/gallery/styles.ts","../src/gallery/template.ts","../src/gallery/index.ts"],"sourcesContent":["import { randomBytes, timingSafeEqual } from \"node:crypto\";\nimport fs from \"node:fs\";\nimport type { IncomingMessage } from \"node:http\";\nimport path from \"node:path\";\n\nexport const DEFAULT_API_BODY_LIMIT_BYTES = 1024 * 1024;\n\nexport class HttpError extends Error {\n readonly status: number;\n\n constructor(message: string, status: number) {\n super(message);\n this.name = \"HttpError\";\n this.status = status;\n }\n}\n\nexport function createDevSessionToken(): string {\n return randomBytes(32).toString(\"base64url\");\n}\n\nfunction realpathNearest(targetPath: string): string {\n let current = path.resolve(targetPath);\n const missingParts: string[] = [];\n\n while (true) {\n try {\n const real = fs.realpathSync.native(current);\n return missingParts.length > 0 ? path.join(real, ...missingParts.reverse()) : real;\n } catch {\n const parent = path.dirname(current);\n if (parent === current) {\n return path.resolve(targetPath);\n }\n missingParts.push(path.basename(current));\n current = parent;\n }\n }\n}\n\nfunction isResolvedPathInside(parentDir: string, candidatePath: string): boolean {\n const parent = path.resolve(parentDir);\n const candidate = path.resolve(candidatePath);\n const relative = path.relative(parent, candidate);\n return relative === \"\" || (!relative.startsWith(\"..\") && !path.isAbsolute(relative));\n}\n\nexport function isPathInside(parentDir: string, candidatePath: string): boolean {\n return isResolvedPathInside(realpathNearest(parentDir), realpathNearest(candidatePath));\n}\n\nexport function isPathInsideAny(parentDirs: string[], candidatePath: string): boolean {\n const candidate = realpathNearest(candidatePath);\n return parentDirs.some((parentDir) =>\n isResolvedPathInside(realpathNearest(parentDir), candidate),\n );\n}\n\nexport function resolveInside(parentDir: string, candidatePath: string, label = \"path\"): string {\n return resolveInsideAny([parentDir], candidatePath, label);\n}\n\nexport function resolveInsideAny(\n parentDirs: string[],\n candidatePath: string,\n label = \"path\",\n): string {\n if (candidatePath.includes(\"\\0\")) {\n throw new HttpError(`${label} contains an invalid character`, 400);\n }\n\n if (parentDirs.length === 0) {\n throw new HttpError(`No allowed directories configured for ${label}`, 500);\n }\n\n const parent = path.resolve(parentDirs[0] ?? \".\");\n const resolved = path.isAbsolute(candidatePath)\n ? path.resolve(candidatePath)\n : path.resolve(parent, candidatePath);\n\n if (!isPathInsideAny(parentDirs, resolved)) {\n throw new HttpError(`${label} escapes the allowed directory`, 400);\n }\n\n return resolved;\n}\n\nexport function resolveUrlPathInside(\n parentDir: string,\n requestUrl: string,\n label = \"path\",\n): string {\n const rawPath = requestUrl.split(/[?#]/, 1)[0] || \"/\";\n let pathname = decodeUrlComponent(rawPath, label);\n\n pathname = pathname.replaceAll(\"\\\\\", \"/\");\n if (pathname.split(\"/\").includes(\"..\")) {\n throw new HttpError(`${label} must not contain parent directory segments`, 400);\n }\n\n const relativePath = `.${pathname}`;\n return resolveInside(parentDir, relativePath, label);\n}\n\nexport function decodeUrlComponent(value: string, label = \"path\"): string {\n try {\n return decodeURIComponent(value);\n } catch {\n throw new HttpError(`${label} is not valid URL encoding`, 400);\n }\n}\n\nexport function collectRequestBody(\n req: IncomingMessage,\n limit = DEFAULT_API_BODY_LIMIT_BYTES,\n): Promise<string> {\n return new Promise((resolve, reject) => {\n let body = \"\";\n let size = 0;\n let completed = false;\n\n req.on(\"data\", (chunk: Buffer | string) => {\n if (completed) return;\n\n const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);\n size += buffer.byteLength;\n if (size > limit) {\n completed = true;\n reject(new HttpError(`Request body exceeds ${limit} bytes`, 413));\n return;\n }\n\n body += buffer.toString(\"utf-8\");\n });\n\n req.on(\"end\", () => {\n if (!completed) {\n completed = true;\n resolve(body);\n }\n });\n\n req.on(\"error\", (error) => {\n if (!completed) {\n completed = true;\n reject(error);\n }\n });\n });\n}\n\nexport function validateDevApiRequest(\n req: IncomingMessage,\n sessionToken: string,\n): HttpError | null {\n const originError = validateOrigin(req);\n if (originError) return originError;\n\n if (!isUnsafeMethod(req.method)) {\n return null;\n }\n\n if (!hasValidSessionToken(req, sessionToken)) {\n return new HttpError(\"Invalid Musea dev session token\", 403);\n }\n\n if (!isJsonRequest(req)) {\n return new HttpError(\"Content-Type must be application/json\", 415);\n }\n\n return null;\n}\n\nexport function serializeScriptValue(value: unknown): string {\n return (JSON.stringify(value) ?? \"undefined\").replace(/[<>&\\u2028\\u2029]/g, (char) => {\n switch (char) {\n case \"<\":\n return \"\\\\u003C\";\n case \">\":\n return \"\\\\u003E\";\n case \"&\":\n return \"\\\\u0026\";\n case \"\\u2028\":\n return \"\\\\u2028\";\n case \"\\u2029\":\n return \"\\\\u2029\";\n default:\n return char;\n }\n });\n}\n\nfunction isUnsafeMethod(method: string | undefined): boolean {\n return method === \"POST\" || method === \"PUT\" || method === \"PATCH\" || method === \"DELETE\";\n}\n\nfunction isJsonRequest(req: IncomingMessage): boolean {\n const contentType = getHeader(req, \"content-type\");\n return contentType?.split(\";\")[0]?.trim().toLowerCase() === \"application/json\";\n}\n\nfunction validateOrigin(req: IncomingMessage): HttpError | null {\n const secFetchSite = getHeader(req, \"sec-fetch-site\");\n if (secFetchSite === \"cross-site\") {\n return new HttpError(\"Cross-origin Musea API requests are not allowed\", 403);\n }\n\n const origin = getHeader(req, \"origin\");\n if (!origin) return null;\n\n const host = getHeader(req, \"host\");\n if (!host) {\n return new HttpError(\"Missing Host header\", 400);\n }\n\n try {\n const originUrl = new URL(origin);\n if (originUrl.host !== host) {\n return new HttpError(\"Cross-origin Musea API requests are not allowed\", 403);\n }\n } catch {\n return new HttpError(\"Invalid Origin header\", 400);\n }\n\n return null;\n}\n\nfunction hasValidSessionToken(req: IncomingMessage, expectedToken: string): boolean {\n const actualToken = getHeader(req, \"x-musea-session\");\n if (!actualToken) return false;\n\n const actual = Buffer.from(actualToken);\n const expected = Buffer.from(expectedToken);\n return actual.length === expected.length && timingSafeEqual(actual, expected);\n}\n\nfunction getHeader(req: IncomingMessage, name: string): string | undefined {\n const value = req.headers[name];\n if (Array.isArray(value)) return value[0];\n return value;\n}\n",":root {\n --musea-bg-primary: #e6e2d6;\n --musea-bg-secondary: #ddd9cd;\n --musea-bg-tertiary: #d4d0c4;\n --musea-bg-elevated: #e6e2d6;\n --musea-accent: #121212;\n --musea-accent-hover: #2a2a2a;\n --musea-accent-subtle: rgba(18, 18, 18, 0.08);\n --musea-text: #121212;\n --musea-text-secondary: #3a3a3a;\n --musea-text-muted: #6b6b6b;\n --musea-border: #c8c4b8;\n --musea-border-subtle: #d4d0c4;\n --musea-success: #16a34a;\n --musea-shadow: 0 4px 24px rgba(0, 0, 0, 0.08);\n --musea-radius-sm: 4px;\n --musea-radius-md: 6px;\n --musea-radius-lg: 8px;\n --musea-transition: 0.15s ease;\n}\n\n* {\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n}\n\nbody {\n font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n background: var(--musea-bg-primary);\n color: var(--musea-text);\n min-height: 100vh;\n line-height: 1.5;\n -webkit-font-smoothing: antialiased;\n}\n","/* Header */\n.header {\n background: var(--musea-bg-secondary);\n border-bottom: 1px solid var(--musea-border);\n padding: 0 1.5rem;\n height: 56px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n position: sticky;\n top: 0;\n z-index: 100;\n}\n\n.header-left {\n display: flex;\n align-items: center;\n gap: 1.5rem;\n}\n\n.logo {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n font-size: 1.125rem;\n font-weight: 700;\n color: var(--musea-accent);\n text-decoration: none;\n}\n\n.logo-svg {\n width: 32px;\n height: 32px;\n flex-shrink: 0;\n}\n\n.logo-icon svg {\n width: 16px;\n height: 16px;\n color: var(--musea-text);\n}\n\n.header-subtitle {\n color: var(--musea-text-muted);\n font-size: 0.8125rem;\n font-weight: 500;\n padding-left: 1.5rem;\n border-left: 1px solid var(--musea-border);\n}\n\n.search-container {\n position: relative;\n width: 280px;\n}\n\n.search-input {\n width: 100%;\n background: var(--musea-bg-tertiary);\n border: 1px solid var(--musea-border);\n border-radius: var(--musea-radius-md);\n padding: 0.5rem 0.75rem 0.5rem 2.25rem;\n color: var(--musea-text);\n font-size: 0.8125rem;\n outline: none;\n transition:\n border-color var(--musea-transition),\n background var(--musea-transition);\n}\n\n.search-input::placeholder {\n color: var(--musea-text-muted);\n}\n\n.search-input:focus {\n border-color: var(--musea-accent);\n background: var(--musea-bg-elevated);\n}\n\n.search-icon {\n position: absolute;\n left: 0.75rem;\n top: 50%;\n transform: translateY(-50%);\n color: var(--musea-text-muted);\n pointer-events: none;\n}\n\n/* Layout */\n.main {\n display: grid;\n grid-template-columns: 260px 1fr;\n min-height: calc(100vh - 56px);\n}\n\n/* Sidebar */\n.sidebar {\n background: var(--musea-bg-secondary);\n border-right: 1px solid var(--musea-border);\n overflow-y: auto;\n overflow-x: hidden;\n}\n\n.sidebar::-webkit-scrollbar {\n width: 6px;\n}\n\n.sidebar::-webkit-scrollbar-track {\n background: transparent;\n}\n\n.sidebar::-webkit-scrollbar-thumb {\n background: var(--musea-border);\n border-radius: 3px;\n}\n\n.sidebar-section {\n padding: 0.75rem;\n}\n\n.category-header {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n padding: 0.625rem 0.75rem;\n font-size: 0.6875rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n color: var(--musea-text-muted);\n cursor: pointer;\n user-select: none;\n border-radius: var(--musea-radius-sm);\n transition: background var(--musea-transition);\n}\n\n.category-header:hover {\n background: var(--musea-bg-tertiary);\n}\n\n.category-icon {\n width: 16px;\n height: 16px;\n transition: transform var(--musea-transition);\n}\n\n.category-header.collapsed .category-icon {\n transform: rotate(-90deg);\n}\n\n.category-count {\n margin-left: auto;\n background: var(--musea-bg-tertiary);\n padding: 0.125rem 0.375rem;\n border-radius: 4px;\n font-size: 0.625rem;\n}\n\n.art-list {\n list-style: none;\n margin-top: 0.25rem;\n}\n\n.art-item {\n display: flex;\n align-items: center;\n gap: 0.625rem;\n padding: 0.5rem 0.75rem 0.5rem 1.75rem;\n border-radius: var(--musea-radius-sm);\n cursor: pointer;\n font-size: 0.8125rem;\n color: var(--musea-text-secondary);\n transition: all var(--musea-transition);\n position: relative;\n}\n\n.art-item::before {\n content: \"\";\n position: absolute;\n left: 0.75rem;\n top: 50%;\n transform: translateY(-50%);\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: var(--musea-border);\n transition: background var(--musea-transition);\n}\n\n.art-item:hover {\n background: var(--musea-bg-tertiary);\n color: var(--musea-text);\n}\n\n.art-item:hover::before {\n background: var(--musea-text-muted);\n}\n\n.art-item.active {\n background: var(--musea-accent-subtle);\n color: var(--musea-accent-hover);\n}\n\n.art-item.active::before {\n background: var(--musea-accent);\n}\n\n.art-variant-count {\n margin-left: auto;\n font-size: 0.6875rem;\n color: var(--musea-text-muted);\n opacity: 0;\n transition: opacity var(--musea-transition);\n}\n\n.art-item:hover .art-variant-count {\n opacity: 1;\n}\n\n/* Content */\n.content {\n background: var(--musea-bg-primary);\n overflow-y: auto;\n}\n\n.content-inner {\n max-width: 1400px;\n margin: 0 auto;\n padding: 2rem;\n}\n\n.content-header {\n margin-bottom: 2rem;\n}\n\n.content-title {\n font-size: 1.5rem;\n font-weight: 700;\n margin-bottom: 0.5rem;\n}\n\n.content-description {\n color: var(--musea-text-muted);\n font-size: 0.9375rem;\n max-width: 600px;\n}\n\n.content-meta {\n display: flex;\n align-items: center;\n gap: 1rem;\n margin-top: 1rem;\n}\n\n.meta-tag {\n display: inline-flex;\n align-items: center;\n gap: 0.375rem;\n padding: 0.25rem 0.625rem;\n background: var(--musea-bg-secondary);\n border: 1px solid var(--musea-border);\n border-radius: var(--musea-radius-sm);\n font-size: 0.75rem;\n color: var(--musea-text-muted);\n}\n\n.meta-tag svg {\n width: 12px;\n height: 12px;\n}\n","/* Gallery Grid */\n.gallery {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));\n gap: 1.25rem;\n}\n\n/* Variant Card */\n.variant-card {\n background: var(--musea-bg-secondary);\n border: 1px solid var(--musea-border);\n border-radius: var(--musea-radius-lg);\n overflow: hidden;\n}\n\n.variant-preview {\n aspect-ratio: 16 / 7;\n background: var(--musea-bg-tertiary);\n display: flex;\n align-items: center;\n justify-content: center;\n box-sizing: border-box;\n position: relative;\n overflow: hidden;\n}\n\n.variant-preview iframe {\n width: 70%;\n height: 100%;\n max-width: 100%;\n max-height: 100%;\n border: none;\n background: white;\n border-radius: var(--musea-radius-md);\n box-shadow: 0 12px 30px rgba(13, 13, 13, 0.12);\n}\n\n.variant-preview-placeholder {\n color: var(--musea-text-muted);\n font-size: 0.8125rem;\n text-align: center;\n padding: 1rem;\n}\n\n.variant-preview-code {\n font-family: \"JetBrains Mono\", \"SF Mono\", \"Fira Code\", monospace;\n font-size: 0.75rem;\n color: var(--musea-text-muted);\n background: var(--musea-bg-primary);\n padding: 1rem;\n overflow: auto;\n max-height: 100%;\n width: 100%;\n}\n\n.variant-info {\n padding: 1rem;\n border-top: 1px solid var(--musea-border);\n display: flex;\n align-items: center;\n justify-content: space-between;\n}\n\n.variant-name {\n font-weight: 600;\n font-size: 0.875rem;\n}\n\n.variant-badge {\n font-size: 0.625rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n padding: 0.1875rem 0.5rem;\n border-radius: 4px;\n background: var(--musea-accent-subtle);\n color: var(--musea-accent);\n}\n\n.variant-actions {\n display: flex;\n gap: 0.5rem;\n}\n\n.variant-action-btn {\n width: 28px;\n height: 28px;\n border: none;\n background: var(--musea-bg-tertiary);\n border-radius: var(--musea-radius-sm);\n color: var(--musea-text-muted);\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: all var(--musea-transition);\n}\n\n.variant-action-btn:hover {\n background: var(--musea-bg-elevated);\n color: var(--musea-text);\n}\n\n.variant-action-btn svg {\n width: 14px;\n height: 14px;\n}\n\n/* Empty State */\n.empty-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n min-height: 400px;\n text-align: center;\n padding: 2rem;\n}\n\n.empty-state-icon {\n width: 80px;\n height: 80px;\n background: var(--musea-bg-secondary);\n border-radius: var(--musea-radius-lg);\n display: flex;\n align-items: center;\n justify-content: center;\n margin-bottom: 1.5rem;\n}\n\n.empty-state-icon svg {\n width: 40px;\n height: 40px;\n color: var(--musea-text-muted);\n}\n\n.empty-state-title {\n font-size: 1.125rem;\n font-weight: 600;\n margin-bottom: 0.5rem;\n}\n\n.empty-state-text {\n color: var(--musea-text-muted);\n font-size: 0.875rem;\n max-width: 300px;\n}\n\n/* Loading */\n.loading {\n display: flex;\n align-items: center;\n justify-content: center;\n min-height: 200px;\n color: var(--musea-text-muted);\n gap: 0.75rem;\n}\n\n.loading-spinner {\n width: 20px;\n height: 20px;\n border: 2px solid var(--musea-border);\n border-top-color: var(--musea-accent);\n border-radius: 50%;\n animation: spin 0.8s linear infinite;\n}\n\n@keyframes spin {\n to {\n transform: rotate(360deg);\n }\n}\n\n/* Responsive */\n@media (max-width: 768px) {\n .main {\n grid-template-columns: 1fr;\n }\n .sidebar {\n display: none;\n }\n .header-subtitle {\n display: none;\n }\n}\n","/**\n * CSS theme variables and style definitions for the Musea gallery.\n *\n * CSS is split into separate .css files and imported as text\n * via tsdown's `?inline` support.\n */\n\n// @ts-expect-error -- CSS imported as text via tsdown `?inline`\nimport stylesBase from \"./styles-base.css?inline\";\n// @ts-expect-error -- CSS imported as text via tsdown `?inline`\nimport stylesLayout from \"./styles-layout.css?inline\";\n// @ts-expect-error -- CSS imported as text via tsdown `?inline`\nimport stylesComponents from \"./styles-components.css?inline\";\n\n/**\n * Generate the full gallery CSS styles string.\n */\nexport function generateGalleryStyles(): string {\n return `${stylesBase}\\n${stylesLayout}\\n${stylesComponents}`;\n}\n","/**\n * HTML structure and inline JS generation for the Musea gallery.\n *\n * Extracted from gallery.ts to keep file sizes manageable.\n */\n\nimport { serializeScriptValue } from \"../security.js\";\n\nfunction escapeHtmlAttribute(value: string): string {\n return value.replace(/[&<>\"']/g, (char) => {\n switch (char) {\n case \"&\":\n return \"&amp;\";\n case \"<\":\n return \"&lt;\";\n case \">\":\n return \"&gt;\";\n case '\"':\n return \"&quot;\";\n case \"'\":\n return \"&#39;\";\n default:\n return char;\n }\n });\n}\n\n/**\n * Generate the gallery HTML body (header, sidebar, content, and inline script).\n */\nexport function generateGalleryBody(basePath: string): string {\n const escapedBasePath = escapeHtmlAttribute(basePath);\n\n return `\n <header class=\"header\">\n <div class=\"header-left\">\n <a href=\"${escapedBasePath}\" class=\"logo\">\n <svg class=\"logo-svg\" width=\"32\" height=\"32\" viewBox=\"0 0 200 200\" fill=\"none\">\n <g transform=\"translate(30, 25) scale(1.2)\">\n <g transform=\"translate(15, 10) skewX(-15)\">\n <path d=\"M 65 0 L 40 60 L 70 20 L 65 0 Z\" fill=\"currentColor\"/>\n <path d=\"M 20 0 L 40 60 L 53 13 L 20 0 Z\" fill=\"currentColor\"/>\n </g>\n </g>\n <g transform=\"translate(110, 120)\">\n <line x1=\"5\" y1=\"10\" x2=\"5\" y2=\"50\" stroke=\"currentColor\" stroke-width=\"3\" stroke-linecap=\"round\"/>\n <line x1=\"60\" y1=\"10\" x2=\"60\" y2=\"50\" stroke=\"currentColor\" stroke-width=\"3\" stroke-linecap=\"round\"/>\n <path d=\"M 0 10 L 32.5 0 L 65 10\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n <rect x=\"15\" y=\"18\" width=\"14\" height=\"12\" rx=\"1\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" opacity=\"0.7\"/>\n <rect x=\"36\" y=\"18\" width=\"14\" height=\"12\" rx=\"1\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" opacity=\"0.7\"/>\n <rect x=\"23\" y=\"35\" width=\"18\" height=\"12\" rx=\"1\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" opacity=\"0.6\"/>\n </g>\n </svg>\n Musea\n </a>\n <span class=\"header-subtitle\">Component Gallery</span>\n </div>\n <div class=\"search-container\">\n <svg class=\"search-icon\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <circle cx=\"11\" cy=\"11\" r=\"8\"/><path d=\"m21 21-4.35-4.35\"/>\n </svg>\n <input type=\"text\" class=\"search-input\" placeholder=\"Search components...\" id=\"search\">\n </div>\n </header>\n\n <main class=\"main\">\n <aside class=\"sidebar\" id=\"sidebar\">\n <div class=\"loading\">\n <div class=\"loading-spinner\"></div>\n Loading...\n </div>\n </aside>\n <section class=\"content\" id=\"content\">\n <div class=\"empty-state\">\n <div class=\"empty-state-icon\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\">\n <path d=\"M4 5a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V5Z\"/>\n <path d=\"M4 13a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1v-6Z\"/>\n <path d=\"M16 13a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1v-6Z\"/>\n </svg>\n </div>\n <div class=\"empty-state-title\">Select a component</div>\n <div class=\"empty-state-text\">Choose a component from the sidebar to view its variants and documentation</div>\n </div>\n </section>\n </main>`;\n}\n\n/**\n * Generate the gallery inline script (SPA logic).\n */\nexport function generateGalleryScript(basePath: string): string {\n return `\n const basePath = ${serializeScriptValue(basePath)};\n let arts = [];\n let selectedArt = null;\n let searchQuery = '';\n\n async function loadArts() {\n try {\n const res = await fetch(basePath + '/api/arts');\n arts = await res.json();\n renderSidebar();\n } catch (e) {\n console.error('Failed to load arts:', e);\n document.getElementById('sidebar').innerHTML = '<div class=\"loading\">Failed to load</div>';\n }\n }\n\n function renderSidebar() {\n const sidebar = document.getElementById('sidebar');\n const categories = {};\n\n const filtered = searchQuery\n ? arts.filter(a => a.metadata.title.toLowerCase().includes(searchQuery.toLowerCase()))\n : arts;\n\n for (const art of filtered) {\n const cat = art.metadata.category || 'Components';\n if (!categories[cat]) categories[cat] = [];\n categories[cat].push(art);\n }\n\n if (Object.keys(categories).length === 0) {\n sidebar.innerHTML = '<div class=\"sidebar-section\"><div class=\"loading\">No components found</div></div>';\n return;\n }\n\n let html = '';\n for (const [category, items] of Object.entries(categories)) {\n const escapedCategory = escapeHtml(category);\n html += '<div class=\"sidebar-section\">';\n html += '<div class=\"category-header\" data-category=\"' + escapedCategory + '\">';\n html += '<svg class=\"category-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"m9 18 6-6-6-6\"/></svg>';\n html += '<span>' + escapedCategory + '</span>';\n html += '<span class=\"category-count\">' + items.length + '</span>';\n html += '</div>';\n html += '<ul class=\"art-list\" data-category=\"' + escapedCategory + '\">';\n for (const art of items) {\n const active = selectedArt?.path === art.path ? 'active' : '';\n const variantCount = art.variants?.length || 0;\n html += '<li class=\"art-item ' + active + '\" data-path=\"' + escapeHtml(art.path) + '\">';\n html += '<span>' + escapeHtml(art.metadata.title) + '</span>';\n html += '<span class=\"art-variant-count\">' + variantCount + ' variant' + (variantCount !== 1 ? 's' : '') + '</span>';\n html += '</li>';\n }\n html += '</ul>';\n html += '</div>';\n }\n\n sidebar.innerHTML = html;\n\n sidebar.querySelectorAll('.art-item').forEach(item => {\n item.addEventListener('click', () => {\n const artPath = item.dataset.path;\n selectedArt = arts.find(a => a.path === artPath);\n renderSidebar();\n renderContent();\n });\n });\n\n sidebar.querySelectorAll('.category-header').forEach(header => {\n header.addEventListener('click', () => {\n header.classList.toggle('collapsed');\n const list = header.parentElement?.querySelector('.art-list');\n if (list) list.style.display = header.classList.contains('collapsed') ? 'none' : 'block';\n });\n });\n }\n\n function renderContent() {\n const content = document.getElementById('content');\n if (!selectedArt) {\n content.innerHTML = \\`\n <div class=\"empty-state\">\n <div class=\"empty-state-icon\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\">\n <path d=\"M4 5a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V5Z\"/>\n <path d=\"M4 13a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1v-6Z\"/>\n <path d=\"M16 13a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1v-6Z\"/>\n </svg>\n </div>\n <div class=\"empty-state-title\">Select a component</div>\n <div class=\"empty-state-text\">Choose a component from the sidebar to view its variants</div>\n </div>\n \\`;\n return;\n }\n\n const meta = selectedArt.metadata;\n const tags = meta.tags || [];\n const variantCount = selectedArt.variants?.length || 0;\n\n let html = '<div class=\"content-inner\">';\n html += '<div class=\"content-header\">';\n html += '<h1 class=\"content-title\">' + escapeHtml(meta.title) + '</h1>';\n if (meta.description) {\n html += '<p class=\"content-description\">' + escapeHtml(meta.description) + '</p>';\n }\n html += '<div class=\"content-meta\">';\n html += '<span class=\"meta-tag\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><rect x=\"3\" y=\"3\" width=\"7\" height=\"7\"/><rect x=\"14\" y=\"3\" width=\"7\" height=\"7\"/><rect x=\"3\" y=\"14\" width=\"7\" height=\"7\"/><rect x=\"14\" y=\"14\" width=\"7\" height=\"7\"/></svg>' + variantCount + ' variant' + (variantCount !== 1 ? 's' : '') + '</span>';\n if (meta.category) {\n html += '<span class=\"meta-tag\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z\"/></svg>' + escapeHtml(meta.category) + '</span>';\n }\n for (const tag of tags) {\n html += '<span class=\"meta-tag\">#' + escapeHtml(tag) + '</span>';\n }\n html += '</div>';\n html += '</div>';\n\n html += '<div class=\"gallery\">';\n for (const variant of selectedArt.variants) {\n const previewUrl = basePath + '/preview?art=' + encodeURIComponent(selectedArt.path) + '&variant=' + encodeURIComponent(variant.name);\n const escapedPreviewUrl = escapeHtml(previewUrl);\n\n html += '<div class=\"variant-card\">';\n html += '<div class=\"variant-preview\">';\n html += '<iframe src=\"' + escapedPreviewUrl + '\" loading=\"lazy\" title=\"' + escapeHtml(variant.name) + '\"></iframe>';\n html += '</div>';\n html += '<div class=\"variant-info\">';\n html += '<div>';\n html += '<span class=\"variant-name\">' + escapeHtml(variant.name) + '</span>';\n if (variant.isDefault) html += ' <span class=\"variant-badge\">Default</span>';\n html += '</div>';\n html += '<div class=\"variant-actions\">';\n html += '<button class=\"variant-action-btn\" title=\"Open in new tab\" data-preview-url=\"' + escapedPreviewUrl + '\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6\"/><polyline points=\"15 3 21 3 21 9\"/><line x1=\"10\" y1=\"14\" x2=\"21\" y2=\"3\"/></svg></button>';\n html += '</div>';\n html += '</div>';\n html += '</div>';\n }\n html += '</div>';\n html += '</div>';\n\n content.innerHTML = html;\n\n content.querySelectorAll('.variant-action-btn[data-preview-url]').forEach(button => {\n button.addEventListener('click', () => {\n const previewUrl = button.dataset.previewUrl;\n if (previewUrl) window.open(previewUrl, '_blank', 'noopener');\n });\n });\n }\n\n function escapeHtml(str) {\n if (!str) return '';\n return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;').replace(/'/g, '&#39;');\n }\n\n // Search\n document.getElementById('search').addEventListener('input', (e) => {\n searchQuery = e.target.value;\n renderSidebar();\n });\n\n // Keyboard shortcut for search\n document.addEventListener('keydown', (e) => {\n if ((e.metaKey || e.ctrlKey) && e.key === 'k') {\n e.preventDefault();\n document.getElementById('search').focus();\n }\n });\n\n loadArts();`;\n}\n","/**\n * Gallery HTML generation for the Musea component gallery.\n *\n * Contains the inline gallery SPA template (used as a fallback when the\n * pre-built gallery is not available) and the gallery virtual module.\n */\n\nimport { generateGalleryStyles } from \"./styles.js\";\nimport { generateGalleryBody, generateGalleryScript } from \"./template.js\";\nimport { serializeScriptValue } from \"../security.js\";\n\n/**\n * Generate the inline gallery HTML page.\n */\nexport function generateGalleryHtml(\n basePath: string,\n devSessionToken: string,\n themeConfig?: { default: string; custom?: Record<string, unknown> },\n): string {\n const themeScript = themeConfig\n ? `window.__MUSEA_THEME_CONFIG__=${serializeScriptValue(themeConfig)};`\n : \"\";\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Musea - Component Gallery</title>\n <script>window.__MUSEA_BASE_PATH__=${serializeScriptValue(basePath)};window.__MUSEA_SESSION_TOKEN__=${serializeScriptValue(devSessionToken)};${themeScript}${\"<\"}/script>\n <style>${generateGalleryStyles()}\n </style>\n</head>\n<body>${generateGalleryBody(basePath)}\n\n <script type=\"module\">${generateGalleryScript(basePath)}\n </script>\n</body>\n</html>`;\n}\n\n/**\n * Generate the virtual gallery module code.\n */\nexport function generateGalleryModule(basePath: string): string {\n return `\nexport const basePath = ${serializeScriptValue(basePath)};\nexport async function loadArts() {\n const res = await fetch(basePath + '/api/arts');\n return res.json();\n}\n`;\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { c as ArtFileInfo, r as MuseaVrtRunner } from "./vrt-CMJXvKjY.mjs";
1
+ import { c as ArtFileInfo, r as MuseaVrtRunner } from "./vrt-CeAvfIsi.mjs";
2
2
  import { Page } from "playwright";
3
3
 
4
4
  //#region src/types/api.d.ts
@@ -148,4 +148,4 @@ declare class MuseaA11yRunner {
148
148
  }
149
149
  //#endregion
150
150
  export { generateA11yJsonReport as a, AnalysisApiResponse as c, generateA11yHtmlReport as i, PaletteApiResponse as l, MuseaA11yRunner as n, A11yOptions as o, computeA11ySummary as r, A11yResult as s, A11ySummary as t };
151
- //# sourceMappingURL=index-CoAc76Ob.d.mts.map
151
+ //# sourceMappingURL=index-BMCTR1nC.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index-CoAc76Ob.d.mts","names":[],"sources":["../src/types/api.ts","../src/a11y/report.ts","../src/a11y/index.ts"],"mappings":";;;;;;;UAGiB,kBAAA;EACf,KAAA;EACA,QAAA,EAAU,cAAA;EACV,MAAA;EACA,IAAA;EACA,UAAA;AAAA;;;;UAMe,cAAA;EACf,IAAA;EACA,OAAA,EAAS,WAAA;EACT,aAAA;EACA,WAAA;EACA,QAAA;EACA,OAAA,EAAS,KAAA;IAAQ,KAAA;IAAe,KAAA;EAAA;EAChC,KAAA;IAAU,GAAA;IAAa,GAAA;IAAa,IAAA;EAAA;EACpC,KAAA;AAAA;;;;KAMU,WAAA;;;;UAiBK,mBAAA;EACf,KAAA,EAAO,KAAA;IACL,IAAA;IACA,IAAA;IACA,QAAA;IACA,aAAA;EAAA;EAEF,KAAA;AAAA;;;;UAUe,WAAA;EAbb;EAeF,OAAA;EAZA;EAcA,YAAA;EAdK;EAgBL,YAAA;EAN0B;EAQ1B,KAAA;AAAA;;;;UAMe,UAAA;EACf,OAAA;EACA,WAAA;EACA,UAAA,EAAY,aAAA;EACZ,MAAA;EACA,UAAA;AAAA;;;;UAMe,aAAA;EACf,EAAA;EACA,MAAA;EACA,WAAA;EACA,OAAA;EACA,KAAA;AAAA;;;;;;iBC9Ec,kBAAA,CAAmB,OAAA,EAAS,UAAA,KAAe,WAAA;;;;iBAkB3C,sBAAA,CAAuB,OAAA,EAAS,UAAA,IAAc,OAAA,EAAS,WAAA;ADlBvE;;;AAAA,iBC8JgB,sBAAA,CAAuB,OAAA,EAAS,UAAA;;;;;;UCrJ/B,WAAA;EACf,eAAA;EACA,aAAA;EACA,eAAA;EACA,aAAA;EACA,YAAA;EACA,aAAA;EACA,UAAA;AAAA;;;;cAqBW,eAAA;EAAA,QACH,OAAA;cAEI,OAAA,GAAS,WAAA;EFlCrB;;;;EE+CM,SAAA,CACJ,QAAA,EAAU,WAAA,IACV,OAAA,UACA,SAAA,GAAY,cAAA,GACX,OAAA,CAAQ,UAAA;EFlDD;;;EEiHJ,SAAA,CAAU,IAAA,EAAM,IAAA,EAAM,OAAA,UAAiB,WAAA,WAAsB,OAAA,CAAQ,UAAA;EFhHtE;;AAMP;EE6IE,UAAA,CAAW,OAAA,EAAS,UAAA,KAAe,WAAA;;;;EAOnC,kBAAA,CAAmB,OAAA,EAAS,UAAA;EFnIM;;;EE2IlC,kBAAA,CAAmB,OAAA,EAAS,UAAA;EF1IrB;;;EAAA,QEiJO,YAAA;EF7IZ;;;EAAA,QE2JM,eAAA;EAAA,QAqCA,eAAA;AAAA"}
1
+ {"version":3,"file":"index-BMCTR1nC.d.mts","names":[],"sources":["../src/types/api.ts","../src/a11y/report.ts","../src/a11y/index.ts"],"mappings":";;;;;;;UAGiB,kBAAA;EACf,KAAA;EACA,QAAA,EAAU,cAAA;EACV,MAAA;EACA,IAAA;EACA,UAAA;AAAA;;;;UAMe,cAAA;EACf,IAAA;EACA,OAAA,EAAS,WAAA;EACT,aAAA;EACA,WAAA;EACA,QAAA;EACA,OAAA,EAAS,KAAA;IAAQ,KAAA;IAAe,KAAA;EAAA;EAChC,KAAA;IAAU,GAAA;IAAa,GAAA;IAAa,IAAA;EAAA;EACpC,KAAA;AAAA;;;;KAMU,WAAA;;;;UAiBK,mBAAA;EACf,KAAA,EAAO,KAAA;IACL,IAAA;IACA,IAAA;IACA,QAAA;IACA,aAAA;EAAA;EAEF,KAAA;AAAA;;;;UAUe,WAAA;EAbb;EAeF,OAAA;EAZA;EAcA,YAAA;EAdK;EAgBL,YAAA;EAN0B;EAQ1B,KAAA;AAAA;;;;UAMe,UAAA;EACf,OAAA;EACA,WAAA;EACA,UAAA,EAAY,aAAA;EACZ,MAAA;EACA,UAAA;AAAA;;;;UAMe,aAAA;EACf,EAAA;EACA,MAAA;EACA,WAAA;EACA,OAAA;EACA,KAAA;AAAA;;;;;;iBC9Ec,kBAAA,CAAmB,OAAA,EAAS,UAAA,KAAe,WAAA;;;;iBAkB3C,sBAAA,CAAuB,OAAA,EAAS,UAAA,IAAc,OAAA,EAAS,WAAA;ADlBvE;;;AAAA,iBC8JgB,sBAAA,CAAuB,OAAA,EAAS,UAAA;;;;;;UCrJ/B,WAAA;EACf,eAAA;EACA,aAAA;EACA,eAAA;EACA,aAAA;EACA,YAAA;EACA,aAAA;EACA,UAAA;AAAA;;;;cAqBW,eAAA;EAAA,QACH,OAAA;cAEI,OAAA,GAAS,WAAA;EFlCrB;;;;EE+CM,SAAA,CACJ,QAAA,EAAU,WAAA,IACV,OAAA,UACA,SAAA,GAAY,cAAA,GACX,OAAA,CAAQ,UAAA;EFlDD;;;EEiHJ,SAAA,CAAU,IAAA,EAAM,IAAA,EAAM,OAAA,UAAiB,WAAA,WAAsB,OAAA,CAAQ,UAAA;EFhHtE;;AAMP;EE6IE,UAAA,CAAW,OAAA,EAAS,UAAA,KAAe,WAAA;;;;EAOnC,kBAAA,CAAmB,OAAA,EAAS,UAAA;EFnIM;;;EE2IlC,kBAAA,CAAmB,OAAA,EAAS,UAAA;EF1IrB;;;EAAA,QEiJO,YAAA;EF7IZ;;;EAAA,QE2JM,eAAA;EAAA,QAqCA,eAAA;AAAA"}