Spaces:
Running
Running
Upload 16 files
Browse files- dist/index.js +207 -0
- dist/public/assets/index-Bl0SxKIt.js +0 -0
- dist/public/assets/index-ZmZ09n2-.css +1 -0
- dist/public/create_icon.js +46 -0
- dist/public/generate_icons.html +80 -0
- dist/public/icon-192.png +0 -0
- dist/public/icon-192.svg +6 -0
- dist/public/icon-512.png +0 -0
- dist/public/icon-512.svg +6 -0
- dist/public/icon.svg +8 -0
- dist/public/index.html +47 -0
- dist/public/manifest.json +33 -0
- dist/public/offline.html +89 -0
- dist/public/register-sw.js +29 -0
- dist/public/sw.js +542 -0
- dist/public/waveform-icon.svg +41 -0
dist/index.js
ADDED
@@ -0,0 +1,207 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// server/index.ts
|
2 |
+
import express2 from "express";
|
3 |
+
import { fileURLToPath } from "url";
|
4 |
+
import { dirname } from "path";
|
5 |
+
|
6 |
+
// Get __dirname equivalent for ES modules
|
7 |
+
const __filename = fileURLToPath(import.meta.url);
|
8 |
+
const __dirname = dirname(__filename);
|
9 |
+
|
10 |
+
// server/routes.ts
|
11 |
+
import { createServer } from "http";
|
12 |
+
async function registerRoutes(app2) {
|
13 |
+
app2.get("/api/download-audio/:videoId", async (req, res) => {
|
14 |
+
try {
|
15 |
+
const { videoId } = req.params;
|
16 |
+
const response = await fetch(`https://backendmix.vercel.app/streams/${videoId}`);
|
17 |
+
if (!response.ok) {
|
18 |
+
return res.status(404).json({ error: "Stream not found" });
|
19 |
+
}
|
20 |
+
const data = await response.json();
|
21 |
+
if (data.audioStreams && data.audioStreams.length > 0) {
|
22 |
+
const audioStream = data.audioStreams[0];
|
23 |
+
const audioResponse = await fetch(audioStream.url);
|
24 |
+
if (!audioResponse.ok) {
|
25 |
+
return res.status(404).json({ error: "Audio stream not available" });
|
26 |
+
}
|
27 |
+
res.setHeader("Content-Type", "audio/webm");
|
28 |
+
res.setHeader("Cache-Control", "public, max-age=86400");
|
29 |
+
const audioBuffer = await audioResponse.arrayBuffer();
|
30 |
+
res.send(Buffer.from(audioBuffer));
|
31 |
+
} else {
|
32 |
+
res.status(404).json({ error: "No audio streams available" });
|
33 |
+
}
|
34 |
+
} catch (error) {
|
35 |
+
console.error("Audio download error:", error);
|
36 |
+
res.status(500).json({ error: "Failed to download audio" });
|
37 |
+
}
|
38 |
+
});
|
39 |
+
const httpServer = createServer(app2);
|
40 |
+
return httpServer;
|
41 |
+
}
|
42 |
+
|
43 |
+
// server/vite.ts
|
44 |
+
import express from "express";
|
45 |
+
import fs from "fs";
|
46 |
+
import path2 from "path";
|
47 |
+
import { createServer as createViteServer, createLogger } from "vite";
|
48 |
+
|
49 |
+
// vite.config.ts
|
50 |
+
import { defineConfig } from "vite";
|
51 |
+
import react from "@vitejs/plugin-react";
|
52 |
+
import path from "path";
|
53 |
+
import runtimeErrorOverlay from "@replit/vite-plugin-runtime-error-modal";
|
54 |
+
|
55 |
+
// Get __dirname for vite config
|
56 |
+
const getViteConfigDirname = () => {
|
57 |
+
const __filename = fileURLToPath(import.meta.url);
|
58 |
+
return dirname(__filename);
|
59 |
+
};
|
60 |
+
|
61 |
+
var vite_config_default = defineConfig({
|
62 |
+
plugins: [
|
63 |
+
react(),
|
64 |
+
runtimeErrorOverlay(),
|
65 |
+
...process.env.NODE_ENV !== "production" && process.env.REPL_ID !== undefined ? [
|
66 |
+
await import("@replit/vite-plugin-cartographer").then(
|
67 |
+
(m) => m.cartographer()
|
68 |
+
)
|
69 |
+
] : []
|
70 |
+
],
|
71 |
+
resolve: {
|
72 |
+
alias: {
|
73 |
+
"@": path.resolve(getViteConfigDirname(), "client", "src"),
|
74 |
+
"@shared": path.resolve(getViteConfigDirname(), "shared"),
|
75 |
+
"@assets": path.resolve(getViteConfigDirname(), "attached_assets")
|
76 |
+
}
|
77 |
+
},
|
78 |
+
root: path.resolve(getViteConfigDirname(), "client"),
|
79 |
+
build: {
|
80 |
+
outDir: path.resolve(getViteConfigDirname(), "dist/public"),
|
81 |
+
emptyOutDir: true
|
82 |
+
},
|
83 |
+
server: {
|
84 |
+
fs: {
|
85 |
+
strict: true,
|
86 |
+
deny: ["**/.*"]
|
87 |
+
}
|
88 |
+
}
|
89 |
+
});
|
90 |
+
|
91 |
+
// server/vite.ts
|
92 |
+
import { nanoid } from "nanoid";
|
93 |
+
var viteLogger = createLogger();
|
94 |
+
function log(message, source = "express") {
|
95 |
+
const formattedTime = (new Date()).toLocaleTimeString("en-US", {
|
96 |
+
hour: "numeric",
|
97 |
+
minute: "2-digit",
|
98 |
+
second: "2-digit",
|
99 |
+
hour12: true
|
100 |
+
});
|
101 |
+
console.log(`${formattedTime} [${source}] ${message}`);
|
102 |
+
}
|
103 |
+
async function setupVite(app2, server) {
|
104 |
+
const serverOptions = {
|
105 |
+
middlewareMode: true,
|
106 |
+
hmr: { server },
|
107 |
+
allowedHosts: true
|
108 |
+
};
|
109 |
+
const vite = await createViteServer({
|
110 |
+
...vite_config_default,
|
111 |
+
configFile: false,
|
112 |
+
customLogger: {
|
113 |
+
...viteLogger,
|
114 |
+
error: (msg, options) => {
|
115 |
+
viteLogger.error(msg, options);
|
116 |
+
process.exit(1);
|
117 |
+
}
|
118 |
+
},
|
119 |
+
server: serverOptions,
|
120 |
+
appType: "custom"
|
121 |
+
});
|
122 |
+
app2.use(vite.middlewares);
|
123 |
+
app2.use("*", async (req, res, next) => {
|
124 |
+
const url = req.originalUrl;
|
125 |
+
try {
|
126 |
+
const clientTemplate = path2.resolve(
|
127 |
+
__dirname,
|
128 |
+
"..",
|
129 |
+
"client",
|
130 |
+
"index.html"
|
131 |
+
);
|
132 |
+
let template = await fs.promises.readFile(clientTemplate, "utf-8");
|
133 |
+
template = template.replace(
|
134 |
+
`src="/src/main.tsx"`,
|
135 |
+
`src="/src/main.tsx?v=${nanoid()}"`
|
136 |
+
);
|
137 |
+
const page = await vite.transformIndexHtml(url, template);
|
138 |
+
res.status(200).set({ "Content-Type": "text/html" }).end(page);
|
139 |
+
} catch (e) {
|
140 |
+
vite.ssrFixStacktrace(e);
|
141 |
+
next(e);
|
142 |
+
}
|
143 |
+
});
|
144 |
+
}
|
145 |
+
function serveStatic(app2) {
|
146 |
+
const distPath = path2.resolve(__dirname, "public");
|
147 |
+
if (!fs.existsSync(distPath)) {
|
148 |
+
throw new Error(
|
149 |
+
`Could not find the build directory: ${distPath}, make sure to build the client first`
|
150 |
+
);
|
151 |
+
}
|
152 |
+
app2.use(express.static(distPath));
|
153 |
+
app2.use("*", (_req, res) => {
|
154 |
+
res.sendFile(path2.resolve(distPath, "index.html"));
|
155 |
+
});
|
156 |
+
}
|
157 |
+
|
158 |
+
// server/index.ts
|
159 |
+
var app = express2();
|
160 |
+
app.use(express2.json());
|
161 |
+
app.use(express2.urlencoded({ extended: false }));
|
162 |
+
app.use((req, res, next) => {
|
163 |
+
const start = Date.now();
|
164 |
+
const path3 = req.path;
|
165 |
+
let capturedJsonResponse = undefined;
|
166 |
+
const originalResJson = res.json;
|
167 |
+
res.json = function(bodyJson, ...args) {
|
168 |
+
capturedJsonResponse = bodyJson;
|
169 |
+
return originalResJson.apply(res, [bodyJson, ...args]);
|
170 |
+
};
|
171 |
+
res.on("finish", () => {
|
172 |
+
const duration = Date.now() - start;
|
173 |
+
if (path3.startsWith("/api")) {
|
174 |
+
let logLine = `${req.method} ${path3} ${res.statusCode} in ${duration}ms`;
|
175 |
+
if (capturedJsonResponse) {
|
176 |
+
logLine += ` :: ${JSON.stringify(capturedJsonResponse)}`;
|
177 |
+
}
|
178 |
+
if (logLine.length > 80) {
|
179 |
+
logLine = logLine.slice(0, 79) + "…";
|
180 |
+
}
|
181 |
+
log(logLine);
|
182 |
+
}
|
183 |
+
});
|
184 |
+
next();
|
185 |
+
});
|
186 |
+
(async () => {
|
187 |
+
const server = await registerRoutes(app);
|
188 |
+
app.use((err, _req, res, _next) => {
|
189 |
+
const status = err.status || err.statusCode || 500;
|
190 |
+
const message = err.message || "Internal Server Error";
|
191 |
+
res.status(status).json({ message });
|
192 |
+
throw err;
|
193 |
+
});
|
194 |
+
if (app.get("env") === "development") {
|
195 |
+
await setupVite(app, server);
|
196 |
+
} else {
|
197 |
+
serveStatic(app);
|
198 |
+
}
|
199 |
+
const port = parseInt(process.env.PORT || "7860", 10);
|
200 |
+
server.listen({
|
201 |
+
port,
|
202 |
+
host: "0.0.0.0",
|
203 |
+
reusePort: true
|
204 |
+
}, () => {
|
205 |
+
log(`serving on port ${port}`);
|
206 |
+
});
|
207 |
+
})();
|
dist/public/assets/index-Bl0SxKIt.js
ADDED
The diff for this file is too large to render.
See raw diff
|
|
dist/public/assets/index-ZmZ09n2-.css
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}*{border-color:var(--border)}body{font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";color:var(--foreground);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;background:var(--background);transition:background .3s ease;font-family:Roboto,Arial,sans-serif}body.dynamic-theme-active{background-attachment:fixed!important;min-height:100vh}body.dynamic-theme-active .bg-background{background:transparent!important}body.dynamic-theme-active .bg-card{background:rgba(var(--card-rgb),.8)!important;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px)}:root{transition:all .3s cubic-bezier(.25,.46,.45,.94)}.container{width:100%}@media (min-width: 640px){.container{max-width:640px}}@media (min-width: 768px){.container{max-width:768px}}@media (min-width: 1024px){.container{max-width:1024px}}@media (min-width: 1280px){.container{max-width:1280px}}@media (min-width: 1536px){.container{max-width:1536px}}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.pointer-events-none{pointer-events:none}.pointer-events-auto{pointer-events:auto}.visible{visibility:visible}.invisible{visibility:hidden}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.inset-0{top:0;right:0;bottom:0;left:0}.inset-x-0{left:0;right:0}.inset-y-0{top:0;bottom:0}.-bottom-1{bottom:-.25rem}.-bottom-12{bottom:-3rem}.-left-12{left:-3rem}.-right-1{right:-.25rem}.-right-12{right:-3rem}.-top-12{top:-3rem}.bottom-0{bottom:0}.bottom-16{bottom:4rem}.bottom-2{bottom:.5rem}.bottom-4{bottom:1rem}.left-0{left:0}.left-1{left:.25rem}.left-1\/2{left:50%}.left-2{left:.5rem}.left-3{left:.75rem}.left-4{left:1rem}.left-\[50\%\]{left:50%}.right-0{right:0}.right-1{right:.25rem}.right-16{right:4rem}.right-2{right:.5rem}.right-3{right:.75rem}.right-4{right:1rem}.top-0{top:0}.top-1\.5{top:.375rem}.top-1\/2{top:50%}.top-16{top:4rem}.top-2{top:.5rem}.top-3\.5{top:.875rem}.top-4{top:1rem}.top-\[1px\]{top:1px}.top-\[50\%\]{top:50%}.top-\[60\%\]{top:60%}.top-full{top:100%}.z-10{z-index:10}.z-20{z-index:20}.z-40{z-index:40}.z-50{z-index:50}.z-\[100\]{z-index:100}.z-\[110\]{z-index:110}.z-\[1\]{z-index:1}.z-\[200\]{z-index:200}.z-\[210\]{z-index:210}.-mx-1{margin-left:-.25rem;margin-right:-.25rem}.mx-2{margin-left:.5rem;margin-right:.5rem}.mx-3{margin-left:.75rem;margin-right:.75rem}.mx-3\.5{margin-left:.875rem;margin-right:.875rem}.mx-4{margin-left:1rem;margin-right:1rem}.mx-8{margin-left:2rem;margin-right:2rem}.mx-auto{margin-left:auto;margin-right:auto}.my-0\.5{margin-top:.125rem;margin-bottom:.125rem}.my-1{margin-top:.25rem;margin-bottom:.25rem}.-ml-4{margin-left:-1rem}.-mt-4{margin-top:-1rem}.-mt-8{margin-top:-2rem}.mb-1{margin-bottom:.25rem}.mb-12{margin-bottom:3rem}.mb-16{margin-bottom:4rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mb-8{margin-bottom:2rem}.ml-0\.5{margin-left:.125rem}.ml-1{margin-left:.25rem}.ml-12{margin-left:3rem}.ml-auto{margin-left:auto}.mr-1{margin-right:.25rem}.mr-2{margin-right:.5rem}.mr-3{margin-right:.75rem}.mt-0\.5{margin-top:.125rem}.mt-1\.5{margin-top:.375rem}.mt-12{margin-top:3rem}.mt-2{margin-top:.5rem}.mt-24{margin-top:6rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.mt-auto{margin-top:auto}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.line-clamp-3{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:3}.block{display:block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.aspect-square{aspect-ratio:1 / 1}.aspect-video{aspect-ratio:16 / 9}.size-4{width:1rem;height:1rem}.h-1{height:.25rem}.h-1\.5{height:.375rem}.h-10{height:2.5rem}.h-11{height:2.75rem}.h-12{height:3rem}.h-14{height:3.5rem}.h-16{height:4rem}.h-2{height:.5rem}.h-2\.5{height:.625rem}.h-20{height:5rem}.h-24{height:6rem}.h-3{height:.75rem}.h-3\.5{height:.875rem}.h-32{height:8rem}.h-4{height:1rem}.h-40{height:10rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-7{height:1.75rem}.h-8{height:2rem}.h-9{height:2.25rem}.h-\[1px\]{height:1px}.h-\[calc\(100vh-4rem\)\]{height:calc(100vh - 4rem)}.h-\[var\(--radix-navigation-menu-viewport-height\)\]{height:var(--radix-navigation-menu-viewport-height)}.h-\[var\(--radix-select-trigger-height\)\]{height:var(--radix-select-trigger-height)}.h-auto{height:auto}.h-full{height:100%}.h-px{height:1px}.h-svh{height:100svh}.max-h-\[--radix-context-menu-content-available-height\]{max-height:var(--radix-context-menu-content-available-height)}.max-h-\[--radix-select-content-available-height\]{max-height:var(--radix-select-content-available-height)}.max-h-\[300px\]{max-height:300px}.max-h-screen{max-height:100vh}.min-h-0{min-height:0px}.min-h-\[80px\]{min-height:80px}.min-h-\[calc\(100vh-4rem\)\]{min-height:calc(100vh - 4rem)}.min-h-screen{min-height:100vh}.min-h-svh{min-height:100svh}.w-0{width:0px}.w-1{width:.25rem}.w-10{width:2.5rem}.w-11{width:2.75rem}.w-12{width:3rem}.w-14{width:3.5rem}.w-16{width:4rem}.w-2{width:.5rem}.w-2\.5{width:.625rem}.w-20{width:5rem}.w-24{width:6rem}.w-3{width:.75rem}.w-3\.5{width:.875rem}.w-3\/4{width:75%}.w-32{width:8rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-56{width:14rem}.w-6{width:1.5rem}.w-64{width:16rem}.w-7{width:1.75rem}.w-72{width:18rem}.w-8{width:2rem}.w-80{width:20rem}.w-9{width:2.25rem}.w-\[--sidebar-width\]{width:var(--sidebar-width)}.w-\[100px\]{width:100px}.w-\[1px\]{width:1px}.w-auto{width:auto}.w-full{width:100%}.w-max{width:-moz-max-content;width:max-content}.w-px{width:1px}.min-w-0{min-width:0px}.min-w-10{min-width:2.5rem}.min-w-11{min-width:2.75rem}.min-w-5{min-width:1.25rem}.min-w-9{min-width:2.25rem}.min-w-\[12rem\]{min-width:12rem}.min-w-\[35px\]{min-width:35px}.min-w-\[8rem\]{min-width:8rem}.min-w-\[var\(--radix-select-trigger-width\)\]{min-width:var(--radix-select-trigger-width)}.max-w-2xl{max-width:42rem}.max-w-4xl{max-width:56rem}.max-w-6xl{max-width:72rem}.max-w-7xl{max-width:80rem}.max-w-\[--skeleton-width\]{max-width:var(--skeleton-width)}.max-w-\[1400px\]{max-width:1400px}.max-w-\[1600px\]{max-width:1600px}.max-w-lg{max-width:32rem}.max-w-max{max-width:-moz-max-content;max-width:max-content}.max-w-md{max-width:28rem}.max-w-xs{max-width:20rem}.flex-1{flex:1 1 0%}.flex-shrink-0,.shrink-0{flex-shrink:0}.grow{flex-grow:1}.grow-0{flex-grow:0}.basis-full{flex-basis:100%}.caption-bottom{caption-side:bottom}.border-collapse{border-collapse:collapse}.origin-\[--radix-context-menu-content-transform-origin\]{transform-origin:var(--radix-context-menu-content-transform-origin)}.origin-\[--radix-hover-card-content-transform-origin\]{transform-origin:var(--radix-hover-card-content-transform-origin)}.origin-\[--radix-menubar-content-transform-origin\]{transform-origin:var(--radix-menubar-content-transform-origin)}.origin-\[--radix-popover-content-transform-origin\]{transform-origin:var(--radix-popover-content-transform-origin)}.origin-\[--radix-select-content-transform-origin\]{transform-origin:var(--radix-select-content-transform-origin)}.origin-\[--radix-tooltip-content-transform-origin\]{transform-origin:var(--radix-tooltip-content-transform-origin)}.-translate-x-1\/2{--tw-translate-x: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-translate-x-px{--tw-translate-x: -1px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-translate-y-1\/2{--tw-translate-y: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-\[-50\%\]{--tw-translate-x: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-px{--tw-translate-x: 1px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-y-\[-50\%\]{--tw-translate-y: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rotate-45{--tw-rotate: 45deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rotate-90{--tw-rotate: 90deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes spin{to{transform:rotate(360deg)}}.animate-spin{animation:spin 1s linear infinite}.cursor-default{cursor:default}.cursor-pointer{cursor:pointer}.touch-none{touch-action:none}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.list-none{list-style-type:none}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-row{flex-direction:row}.flex-col{flex-direction:column}.flex-col-reverse{flex-direction:column-reverse}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.items-stretch{align-items:stretch}.justify-start{justify-content:flex-start}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-6{gap:1.5rem}.gap-8{gap:2rem}.space-x-1>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(.25rem * var(--tw-space-x-reverse));margin-left:calc(.25rem * calc(1 - var(--tw-space-x-reverse)))}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(.5rem * var(--tw-space-x-reverse));margin-left:calc(.5rem * calc(1 - var(--tw-space-x-reverse)))}.space-x-3>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(.75rem * var(--tw-space-x-reverse));margin-left:calc(.75rem * calc(1 - var(--tw-space-x-reverse)))}.space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(1rem * var(--tw-space-x-reverse));margin-left:calc(1rem * calc(1 - var(--tw-space-x-reverse)))}.space-x-6>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(1.5rem * var(--tw-space-x-reverse));margin-left:calc(1.5rem * calc(1 - var(--tw-space-x-reverse)))}.space-x-8>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(2rem * var(--tw-space-x-reverse));margin-left:calc(2rem * calc(1 - var(--tw-space-x-reverse)))}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-1\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.375rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.375rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.75rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem * var(--tw-space-y-reverse))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.overflow-x-hidden{overflow-x:hidden}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.break-words{overflow-wrap:break-word}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:1rem}.rounded-\[2px\]{border-radius:2px}.rounded-\[inherit\]{border-radius:inherit}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:var(--radius)}.rounded-md{border-radius:calc(var(--radius) - 2px)}.rounded-sm{border-radius:calc(var(--radius) - 4px)}.rounded-xl{border-radius:.75rem}.rounded-b-2xl{border-bottom-right-radius:1rem;border-bottom-left-radius:1rem}.rounded-r-full{border-top-right-radius:9999px;border-bottom-right-radius:9999px}.rounded-t-\[10px\]{border-top-left-radius:10px;border-top-right-radius:10px}.rounded-tl-sm{border-top-left-radius:calc(var(--radius) - 4px)}.border{border-width:1px}.border-2{border-width:2px}.border-4{border-width:4px}.border-\[1\.5px\]{border-width:1.5px}.border-y{border-top-width:1px;border-bottom-width:1px}.border-b{border-bottom-width:1px}.border-b-2{border-bottom-width:2px}.border-l{border-left-width:1px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-dashed{border-style:dashed}.border-\[--color-border\]{border-color:var(--color-border)}.border-background{border-color:var(--background)}.border-blue-200{--tw-border-opacity: 1;border-color:rgb(191 219 254 / var(--tw-border-opacity, 1))}.border-blue-500{--tw-border-opacity: 1;border-color:rgb(59 130 246 / var(--tw-border-opacity, 1))}.border-border{border-color:var(--border)}.border-destructive{border-color:var(--destructive)}.border-gray-200{--tw-border-opacity: 1;border-color:rgb(229 231 235 / var(--tw-border-opacity, 1))}.border-gray-300{--tw-border-opacity: 1;border-color:rgb(209 213 219 / var(--tw-border-opacity, 1))}.border-green-200{--tw-border-opacity: 1;border-color:rgb(187 247 208 / var(--tw-border-opacity, 1))}.border-green-500{--tw-border-opacity: 1;border-color:rgb(34 197 94 / var(--tw-border-opacity, 1))}.border-input{border-color:var(--input)}.border-orange-200{--tw-border-opacity: 1;border-color:rgb(254 215 170 / var(--tw-border-opacity, 1))}.border-orange-500{--tw-border-opacity: 1;border-color:rgb(249 115 22 / var(--tw-border-opacity, 1))}.border-primary{border-color:var(--primary)}.border-sidebar-border{border-color:var(--sidebar-border)}.border-transparent{border-color:transparent}.border-l-transparent{border-left-color:transparent}.border-t-transparent{border-top-color:transparent}.bg-\[--color-bg\]{background-color:var(--color-bg)}.bg-accent{background-color:var(--accent)}.bg-background{background-color:var(--background)}.bg-black{--tw-bg-opacity: 1;background-color:rgb(0 0 0 / var(--tw-bg-opacity, 1))}.bg-black\/20{background-color:#0003}.bg-black\/80{background-color:#000c}.bg-blue-50{--tw-bg-opacity: 1;background-color:rgb(239 246 255 / var(--tw-bg-opacity, 1))}.bg-blue-500{--tw-bg-opacity: 1;background-color:rgb(59 130 246 / var(--tw-bg-opacity, 1))}.bg-blue-600{--tw-bg-opacity: 1;background-color:rgb(37 99 235 / var(--tw-bg-opacity, 1))}.bg-border{background-color:var(--border)}.bg-card{background-color:var(--card)}.bg-destructive{background-color:var(--destructive)}.bg-foreground{background-color:var(--foreground)}.bg-gray-200{--tw-bg-opacity: 1;background-color:rgb(229 231 235 / var(--tw-bg-opacity, 1))}.bg-gray-300{--tw-bg-opacity: 1;background-color:rgb(209 213 219 / var(--tw-bg-opacity, 1))}.bg-gray-50{--tw-bg-opacity: 1;background-color:rgb(249 250 251 / var(--tw-bg-opacity, 1))}.bg-green-50{--tw-bg-opacity: 1;background-color:rgb(240 253 244 / var(--tw-bg-opacity, 1))}.bg-green-500{--tw-bg-opacity: 1;background-color:rgb(34 197 94 / var(--tw-bg-opacity, 1))}.bg-green-600{--tw-bg-opacity: 1;background-color:rgb(22 163 74 / var(--tw-bg-opacity, 1))}.bg-input{background-color:var(--input)}.bg-muted{background-color:var(--muted)}.bg-orange-50{--tw-bg-opacity: 1;background-color:rgb(255 247 237 / var(--tw-bg-opacity, 1))}.bg-popover{background-color:var(--popover)}.bg-primary{background-color:var(--primary)}.bg-red-500{--tw-bg-opacity: 1;background-color:rgb(239 68 68 / var(--tw-bg-opacity, 1))}.bg-secondary{background-color:var(--secondary)}.bg-sidebar{background-color:var(--sidebar-background)}.bg-sidebar-border{background-color:var(--sidebar-border)}.bg-transparent{background-color:transparent}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}.bg-white\/20{background-color:#fff3}.bg-yellow-500{--tw-bg-opacity: 1;background-color:rgb(234 179 8 / var(--tw-bg-opacity, 1))}.bg-opacity-0{--tw-bg-opacity: 0}.bg-opacity-30{--tw-bg-opacity: .3}.bg-opacity-80{--tw-bg-opacity: .8}.bg-gradient-to-br{background-image:linear-gradient(to bottom right,var(--tw-gradient-stops))}.bg-gradient-to-r{background-image:linear-gradient(to right,var(--tw-gradient-stops))}.bg-gradient-to-t{background-image:linear-gradient(to top,var(--tw-gradient-stops))}.from-black\/60{--tw-gradient-from: rgb(0 0 0 / .6) var(--tw-gradient-from-position);--tw-gradient-to: rgb(0 0 0 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-blue-600{--tw-gradient-from: #2563eb var(--tw-gradient-from-position);--tw-gradient-to: rgb(37 99 235 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-blue-600\/10{--tw-gradient-from: rgb(37 99 235 / .1) var(--tw-gradient-from-position);--tw-gradient-to: rgb(37 99 235 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-green-500{--tw-gradient-from: #22c55e var(--tw-gradient-from-position);--tw-gradient-to: rgb(34 197 94 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-orange-500{--tw-gradient-from: #f97316 var(--tw-gradient-from-position);--tw-gradient-to: rgb(249 115 22 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-primary{--tw-gradient-from: var(--primary) var(--tw-gradient-from-position);--tw-gradient-to: rgb(255 255 255 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.via-black\/20{--tw-gradient-to: rgb(0 0 0 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), rgb(0 0 0 / .2) var(--tw-gradient-via-position), var(--tw-gradient-to)}.to-blue-500{--tw-gradient-to: #3b82f6 var(--tw-gradient-to-position)}.to-orange-600{--tw-gradient-to: #ea580c var(--tw-gradient-to-position)}.to-purple-600{--tw-gradient-to: #9333ea var(--tw-gradient-to-position)}.to-purple-600\/10{--tw-gradient-to: rgb(147 51 234 / .1) var(--tw-gradient-to-position)}.to-transparent{--tw-gradient-to: transparent var(--tw-gradient-to-position)}.fill-current{fill:currentColor}.object-cover{-o-object-fit:cover;object-fit:cover}.p-0{padding:0}.p-1{padding:.25rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.p-8{padding:2rem}.p-\[1px\]{padding:1px}.px-1{padding-left:.25rem;padding-right:.25rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.px-8{padding-left:2rem;padding-right:2rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-12{padding-top:3rem;padding-bottom:3rem}.py-16{padding-top:4rem;padding-bottom:4rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pb-3{padding-bottom:.75rem}.pb-4{padding-bottom:1rem}.pb-8{padding-bottom:2rem}.pl-10{padding-left:2.5rem}.pl-2\.5{padding-left:.625rem}.pl-4{padding-left:1rem}.pl-8{padding-left:2rem}.pr-12{padding-right:3rem}.pr-2{padding-right:.5rem}.pr-2\.5{padding-right:.625rem}.pr-8{padding-right:2rem}.pt-0{padding-top:0}.pt-1{padding-top:.25rem}.pt-16{padding-top:4rem}.pt-3{padding-top:.75rem}.pt-4{padding-top:1rem}.pt-6{padding-top:1.5rem}.text-left{text-align:left}.text-center{text-align:center}.align-middle{vertical-align:middle}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-4xl{font-size:2.25rem;line-height:2.5rem}.text-\[0\.8rem\]{font-size:.8rem}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-normal{font-weight:400}.font-semibold{font-weight:600}.tabular-nums{--tw-numeric-spacing: tabular-nums;font-variant-numeric:var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction)}.leading-5{line-height:1.25rem}.leading-none{line-height:1}.leading-relaxed{line-height:1.625}.leading-tight{line-height:1.25}.tracking-tight{letter-spacing:-.025em}.tracking-widest{letter-spacing:.1em}.text-accent-foreground{color:var(--accent-foreground)}.text-blue-400{--tw-text-opacity: 1;color:rgb(96 165 250 / var(--tw-text-opacity, 1))}.text-blue-500{--tw-text-opacity: 1;color:rgb(59 130 246 / var(--tw-text-opacity, 1))}.text-blue-600{--tw-text-opacity: 1;color:rgb(37 99 235 / var(--tw-text-opacity, 1))}.text-blue-700{--tw-text-opacity: 1;color:rgb(29 78 216 / var(--tw-text-opacity, 1))}.text-card-foreground{color:var(--card-foreground)}.text-current{color:currentColor}.text-destructive{color:var(--destructive)}.text-destructive-foreground{color:var(--destructive-foreground)}.text-foreground{color:var(--foreground)}.text-gray-500{--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity, 1))}.text-gray-600{--tw-text-opacity: 1;color:rgb(75 85 99 / var(--tw-text-opacity, 1))}.text-gray-900{--tw-text-opacity: 1;color:rgb(17 24 39 / var(--tw-text-opacity, 1))}.text-green-500{--tw-text-opacity: 1;color:rgb(34 197 94 / var(--tw-text-opacity, 1))}.text-green-700{--tw-text-opacity: 1;color:rgb(21 128 61 / var(--tw-text-opacity, 1))}.text-muted-foreground{color:var(--muted-foreground)}.text-orange-500{--tw-text-opacity: 1;color:rgb(249 115 22 / var(--tw-text-opacity, 1))}.text-orange-600{--tw-text-opacity: 1;color:rgb(234 88 12 / var(--tw-text-opacity, 1))}.text-orange-800{--tw-text-opacity: 1;color:rgb(154 52 18 / var(--tw-text-opacity, 1))}.text-popover-foreground{color:var(--popover-foreground)}.text-primary{color:var(--primary)}.text-primary-foreground{color:var(--primary-foreground)}.text-red-500{--tw-text-opacity: 1;color:rgb(239 68 68 / var(--tw-text-opacity, 1))}.text-secondary-foreground{color:var(--secondary-foreground)}.text-sidebar-foreground{color:var(--sidebar-foreground)}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.text-white\/90{color:#ffffffe6}.underline-offset-4{text-underline-offset:4px}.opacity-0{opacity:0}.opacity-100{opacity:1}.opacity-50{opacity:.5}.opacity-60{opacity:.6}.opacity-70{opacity:.7}.opacity-90{opacity:.9}.shadow-2xl{--tw-shadow: 0 25px 50px -12px rgb(0 0 0 / .25);--tw-shadow-colored: 0 25px 50px -12px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-\[0_0_0_1px_hsl\(var\(--sidebar-border\)\)\]{--tw-shadow: 0 0 0 1px hsl(var(--sidebar-border));--tw-shadow-colored: 0 0 0 1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-md{--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / .1), 0 2px 4px -2px rgb(0 0 0 / .1);--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-none{--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / .05);--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-xl{--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / .1), 0 8px 10px -6px rgb(0 0 0 / .1);--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.outline-none{outline:2px solid transparent;outline-offset:2px}.outline{outline-style:solid}.ring-0{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-2{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-ring{--tw-ring-color: var(--ring)}.ring-sidebar-ring{--tw-ring-color: var(--sidebar-ring)}.ring-white\/40{--tw-ring-color: rgb(255 255 255 / .4)}.ring-offset-background{--tw-ring-offset-color: var(--background)}.blur{--tw-blur: blur(8px);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.backdrop-blur-3xl{--tw-backdrop-blur: blur(64px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.backdrop-blur-lg{--tw-backdrop-blur: blur(16px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.backdrop-blur-sm{--tw-backdrop-blur: blur(4px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[left\,right\,width\]{transition-property:left,right,width;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[margin\,opacity\]{transition-property:margin,opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[width\,height\,padding\]{transition-property:width,height,padding;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[width\]{transition-property:width;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-1000{transition-duration:1s}.duration-200{transition-duration:.2s}.duration-300{transition-duration:.3s}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}.ease-linear{transition-timing-function:linear}@keyframes enter{0%{opacity:var(--tw-enter-opacity, 1);transform:translate3d(var(--tw-enter-translate-x, 0),var(--tw-enter-translate-y, 0),0) scale3d(var(--tw-enter-scale, 1),var(--tw-enter-scale, 1),var(--tw-enter-scale, 1)) rotate(var(--tw-enter-rotate, 0))}}@keyframes exit{to{opacity:var(--tw-exit-opacity, 1);transform:translate3d(var(--tw-exit-translate-x, 0),var(--tw-exit-translate-y, 0),0) scale3d(var(--tw-exit-scale, 1),var(--tw-exit-scale, 1),var(--tw-exit-scale, 1)) rotate(var(--tw-exit-rotate, 0))}}.animate-in{animation-name:enter;animation-duration:.15s;--tw-enter-opacity: initial;--tw-enter-scale: initial;--tw-enter-rotate: initial;--tw-enter-translate-x: initial;--tw-enter-translate-y: initial}.fade-in-0{--tw-enter-opacity: 0}.fade-in-80{--tw-enter-opacity: .8}.zoom-in-95{--tw-enter-scale: .95}.duration-1000{animation-duration:1s}.duration-200{animation-duration:.2s}.duration-300{animation-duration:.3s}.ease-in-out{animation-timing-function:cubic-bezier(.4,0,.2,1)}.ease-linear{animation-timing-function:linear}.paused{animation-play-state:paused}.scrollbar-hide{-ms-overflow-style:none;scrollbar-width:none}.scrollbar-hide::-webkit-scrollbar{display:none}.line-clamp-2{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.vertical-text{writing-mode:vertical-lr;text-orientation:mixed;transform:rotate(180deg);white-space:nowrap}.bg-youtube-red{background-color:var(--youtube-red)}.slider::-webkit-slider-thumb{-webkit-appearance:none;-moz-appearance:none;appearance:none;height:12px;width:12px;border-radius:50%;background:#1db954;cursor:pointer;border:none;box-shadow:0 0 2px #555}.slider::-webkit-slider-thumb:hover{box-shadow:0 0 0 3px #1db95426}.slider::-moz-range-thumb{height:12px;width:12px;border-radius:50%;background:#1db954;cursor:pointer;border:none;box-shadow:0 0 2px #555}.slider::-moz-range-thumb:hover{box-shadow:0 0 0 3px #1db95426}:root{--background: #ffffff;--foreground: #000000;--muted: #f5f5f5;--muted-foreground: #333333;--popover: #ffffff;--popover-foreground: #000000;--card: #ffffff;--card-foreground: #000000;--border: #e0e0e0;--input: #f9f9f9;--primary: #f97316;--primary-foreground: #ffffff;--secondary: #e0e0e0;--secondary-foreground: #000000;--accent: #eaeaea;--accent-foreground: #000000;--destructive: #dc3545;--destructive-foreground: #ffffff;--ring: #cccccc;--radius: .5rem;--text-secondary: #333333;--text-placeholder: #999999;--text-link: #1a0dab;--text-muted: #cccccc;--input-border: #cccccc;--hover-bg: #eaeaea}.dark{--background: #121212;--foreground: #ffffff;--muted: #1e1e1e;--muted-foreground: #cccccc;--popover: #121212;--popover-foreground: #ffffff;--card: #1f1f1f;--card-foreground: #ffffff;--border: #2a2a2a;--input: #2c2c2c;--primary: #f97316;--primary-foreground: #ffffff;--secondary: #3a3a3a;--secondary-foreground: #ffffff;--accent: #333333;--accent-foreground: #ffffff;--destructive: #dc3545;--destructive-foreground: #ffffff;--ring: #444444;--radius: .5rem;--text-secondary: #cccccc;--text-placeholder: #777777;--text-link: #8ab4f8;--text-muted: #555555;--input-border: #444444;--hover-bg: #333333}body.dynamic-theme-active .data-\[state\=active\]\:bg-background[data-state=active]{background:transparent!important}body.dynamic-theme-active .dark\:bg-card:is(.dark *){background:rgba(var(--card-rgb),.8)!important;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px)}.file\:border-0::file-selector-button{border-width:0px}.file\:bg-transparent::file-selector-button{background-color:transparent}.file\:text-sm::file-selector-button{font-size:.875rem;line-height:1.25rem}.file\:font-medium::file-selector-button{font-weight:500}.file\:text-foreground::file-selector-button{color:var(--foreground)}.placeholder\:text-muted-foreground::-moz-placeholder{color:var(--muted-foreground)}.placeholder\:text-muted-foreground::placeholder{color:var(--muted-foreground)}.after\:absolute:after{content:var(--tw-content);position:absolute}.after\:-inset-2:after{content:var(--tw-content);top:-.5rem;right:-.5rem;bottom:-.5rem;left:-.5rem}.after\:inset-y-0:after{content:var(--tw-content);top:0;bottom:0}.after\:left-1\/2:after{content:var(--tw-content);left:50%}.after\:w-1:after{content:var(--tw-content);width:.25rem}.after\:w-\[2px\]:after{content:var(--tw-content);width:2px}.after\:-translate-x-1\/2:after{content:var(--tw-content);--tw-translate-x: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.first\:rounded-l-md:first-child{border-top-left-radius:calc(var(--radius) - 2px);border-bottom-left-radius:calc(var(--radius) - 2px)}.first\:border-l:first-child{border-left-width:1px}.last\:rounded-r-md:last-child{border-top-right-radius:calc(var(--radius) - 2px);border-bottom-right-radius:calc(var(--radius) - 2px)}.focus-within\:relative:focus-within{position:relative}.focus-within\:z-20:focus-within{z-index:20}.hover\:h-2:hover{height:.5rem}.hover\:scale-105:hover{--tw-scale-x: 1.05;--tw-scale-y: 1.05;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.hover\:rounded-lg:hover{border-radius:var(--radius)}.hover\:bg-accent:hover{background-color:var(--accent)}.hover\:bg-gray-100:hover{--tw-bg-opacity: 1;background-color:rgb(243 244 246 / var(--tw-bg-opacity, 1))}.hover\:bg-green-400:hover{--tw-bg-opacity: 1;background-color:rgb(74 222 128 / var(--tw-bg-opacity, 1))}.hover\:bg-muted:hover{background-color:var(--muted)}.hover\:bg-primary:hover{background-color:var(--primary)}.hover\:bg-secondary:hover{background-color:var(--secondary)}.hover\:bg-sidebar-accent:hover{background-color:var(--sidebar-accent)}.hover\:text-accent-foreground:hover{color:var(--accent-foreground)}.hover\:text-destructive:hover{color:var(--destructive)}.hover\:text-foreground:hover{color:var(--foreground)}.hover\:text-gray-800:hover{--tw-text-opacity: 1;color:rgb(31 41 55 / var(--tw-text-opacity, 1))}.hover\:text-gray-900:hover{--tw-text-opacity: 1;color:rgb(17 24 39 / var(--tw-text-opacity, 1))}.hover\:text-muted-foreground:hover{color:var(--muted-foreground)}.hover\:text-primary-foreground:hover{color:var(--primary-foreground)}.hover\:text-sidebar-accent-foreground:hover{color:var(--sidebar-accent-foreground)}.hover\:underline:hover{text-decoration-line:underline}.hover\:opacity-100:hover{opacity:1}.hover\:shadow-\[0_0_0_1px_hsl\(var\(--sidebar-accent\)\)\]:hover{--tw-shadow: 0 0 0 1px hsl(var(--sidebar-accent));--tw-shadow-colored: 0 0 0 1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.hover\:after\:bg-sidebar-border:hover:after{content:var(--tw-content);background-color:var(--sidebar-border)}.focus\:border-transparent:focus{border-color:transparent}.focus\:bg-accent:focus{background-color:var(--accent)}.focus\:bg-primary:focus{background-color:var(--primary)}.focus\:text-accent-foreground:focus{color:var(--accent-foreground)}.focus\:text-primary-foreground:focus{color:var(--primary-foreground)}.focus\:opacity-100:focus{opacity:1}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-2:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-primary:focus{--tw-ring-color: var(--primary)}.focus\:ring-ring:focus{--tw-ring-color: var(--ring)}.focus\:ring-offset-2:focus{--tw-ring-offset-width: 2px}.focus-visible\:outline-none:focus-visible{outline:2px solid transparent;outline-offset:2px}.focus-visible\:ring-1:focus-visible{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus-visible\:ring-2:focus-visible{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus-visible\:ring-ring:focus-visible{--tw-ring-color: var(--ring)}.focus-visible\:ring-sidebar-ring:focus-visible{--tw-ring-color: var(--sidebar-ring)}.focus-visible\:ring-offset-1:focus-visible{--tw-ring-offset-width: 1px}.focus-visible\:ring-offset-2:focus-visible{--tw-ring-offset-width: 2px}.focus-visible\:ring-offset-background:focus-visible{--tw-ring-offset-color: var(--background)}.active\:bg-sidebar-accent:active{background-color:var(--sidebar-accent)}.active\:text-sidebar-accent-foreground:active{color:var(--sidebar-accent-foreground)}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}.group\/menu-item:focus-within .group-focus-within\/menu-item\:opacity-100{opacity:1}.group:hover .group-hover\:bg-orange-500{--tw-bg-opacity: 1;background-color:rgb(249 115 22 / var(--tw-bg-opacity, 1))}.group:hover .group-hover\:bg-opacity-30{--tw-bg-opacity: .3}.group:hover .group-hover\:text-foreground{color:var(--foreground)}.group:hover .group-hover\:text-orange-600{--tw-text-opacity: 1;color:rgb(234 88 12 / var(--tw-text-opacity, 1))}.group:hover .group-hover\:text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.group\/menu-item:hover .group-hover\/menu-item\:opacity-100,.group:hover .group-hover\:opacity-100{opacity:1}.group.destructive .group-\[\.destructive\]\:text-red-300{--tw-text-opacity: 1;color:rgb(252 165 165 / var(--tw-text-opacity, 1))}.group.destructive .group-\[\.destructive\]\:hover\:bg-destructive:hover{background-color:var(--destructive)}.group.destructive .group-\[\.destructive\]\:hover\:text-destructive-foreground:hover{color:var(--destructive-foreground)}.group.destructive .group-\[\.destructive\]\:hover\:text-red-50:hover{--tw-text-opacity: 1;color:rgb(254 242 242 / var(--tw-text-opacity, 1))}.group.destructive .group-\[\.destructive\]\:focus\:ring-destructive:focus{--tw-ring-color: var(--destructive)}.group.destructive .group-\[\.destructive\]\:focus\:ring-red-400:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(248 113 113 / var(--tw-ring-opacity, 1))}.group.destructive .group-\[\.destructive\]\:focus\:ring-offset-red-600:focus{--tw-ring-offset-color: #dc2626}.peer\/menu-button:hover~.peer-hover\/menu-button\:text-sidebar-accent-foreground{color:var(--sidebar-accent-foreground)}.peer:disabled~.peer-disabled\:cursor-not-allowed{cursor:not-allowed}.peer:disabled~.peer-disabled\:opacity-70{opacity:.7}.has-\[\[data-variant\=inset\]\]\:bg-sidebar:has([data-variant=inset]){background-color:var(--sidebar-background)}.has-\[\:disabled\]\:opacity-50:has(:disabled){opacity:.5}.group\/menu-item:has([data-sidebar=menu-action]) .group-has-\[\[data-sidebar\=menu-action\]\]\/menu-item\:pr-8{padding-right:2rem}.aria-disabled\:pointer-events-none[aria-disabled=true]{pointer-events:none}.aria-disabled\:opacity-50[aria-disabled=true]{opacity:.5}.aria-selected\:bg-accent[aria-selected=true]{background-color:var(--accent)}.aria-selected\:text-accent-foreground[aria-selected=true]{color:var(--accent-foreground)}.aria-selected\:text-muted-foreground[aria-selected=true]{color:var(--muted-foreground)}.aria-selected\:opacity-100[aria-selected=true]{opacity:1}.data-\[disabled\=true\]\:pointer-events-none[data-disabled=true],.data-\[disabled\]\:pointer-events-none[data-disabled]{pointer-events:none}.data-\[panel-group-direction\=vertical\]\:h-px[data-panel-group-direction=vertical]{height:1px}.data-\[panel-group-direction\=vertical\]\:w-full[data-panel-group-direction=vertical]{width:100%}.data-\[side\=bottom\]\:translate-y-1[data-side=bottom]{--tw-translate-y: .25rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.data-\[side\=left\]\:-translate-x-1[data-side=left]{--tw-translate-x: -.25rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.data-\[side\=right\]\:translate-x-1[data-side=right]{--tw-translate-x: .25rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.data-\[side\=top\]\:-translate-y-1[data-side=top]{--tw-translate-y: -.25rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.data-\[state\=checked\]\:translate-x-5[data-state=checked]{--tw-translate-x: 1.25rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.data-\[state\=unchecked\]\:translate-x-0[data-state=unchecked],.data-\[swipe\=cancel\]\:translate-x-0[data-swipe=cancel]{--tw-translate-x: 0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.data-\[swipe\=end\]\:translate-x-\[var\(--radix-toast-swipe-end-x\)\][data-swipe=end]{--tw-translate-x: var(--radix-toast-swipe-end-x);transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.data-\[swipe\=move\]\:translate-x-\[var\(--radix-toast-swipe-move-x\)\][data-swipe=move]{--tw-translate-x: var(--radix-toast-swipe-move-x);transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes accordion-up{0%{height:var(--radix-accordion-content-height)}to{height:0}}.data-\[state\=closed\]\:animate-accordion-up[data-state=closed]{animation:accordion-up .2s ease-out}@keyframes accordion-down{0%{height:0}to{height:var(--radix-accordion-content-height)}}.data-\[state\=open\]\:animate-accordion-down[data-state=open]{animation:accordion-down .2s ease-out}.data-\[panel-group-direction\=vertical\]\:flex-col[data-panel-group-direction=vertical]{flex-direction:column}.data-\[active\=true\]\:bg-sidebar-accent[data-active=true]{background-color:var(--sidebar-accent)}.data-\[selected\=\'true\'\]\:bg-accent[data-selected=true]{background-color:var(--accent)}.data-\[state\=active\]\:bg-background[data-state=active]{background-color:var(--background)}.data-\[state\=checked\]\:bg-primary[data-state=checked]{background-color:var(--primary)}.data-\[state\=on\]\:bg-accent[data-state=on],.data-\[state\=open\]\:bg-accent[data-state=open]{background-color:var(--accent)}.data-\[state\=open\]\:bg-secondary[data-state=open]{background-color:var(--secondary)}.data-\[state\=selected\]\:bg-muted[data-state=selected]{background-color:var(--muted)}.data-\[state\=unchecked\]\:bg-input[data-state=unchecked]{background-color:var(--input)}.data-\[active\=true\]\:font-medium[data-active=true]{font-weight:500}.data-\[active\=true\]\:text-sidebar-accent-foreground[data-active=true]{color:var(--sidebar-accent-foreground)}.data-\[placeholder\]\:text-muted-foreground[data-placeholder]{color:var(--muted-foreground)}.data-\[selected\=true\]\:text-accent-foreground[data-selected=true]{color:var(--accent-foreground)}.data-\[state\=active\]\:text-foreground[data-state=active]{color:var(--foreground)}.data-\[state\=checked\]\:text-primary-foreground[data-state=checked]{color:var(--primary-foreground)}.data-\[state\=on\]\:text-accent-foreground[data-state=on],.data-\[state\=open\]\:text-accent-foreground[data-state=open]{color:var(--accent-foreground)}.data-\[state\=open\]\:text-muted-foreground[data-state=open]{color:var(--muted-foreground)}.data-\[disabled\=true\]\:opacity-50[data-disabled=true],.data-\[disabled\]\:opacity-50[data-disabled]{opacity:.5}.data-\[state\=open\]\:opacity-100[data-state=open]{opacity:1}.data-\[state\=active\]\:shadow-sm[data-state=active]{--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / .05);--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.data-\[swipe\=move\]\:transition-none[data-swipe=move]{transition-property:none}.data-\[state\=closed\]\:duration-300[data-state=closed]{transition-duration:.3s}.data-\[state\=open\]\:duration-500[data-state=open]{transition-duration:.5s}.data-\[motion\^\=from-\]\:animate-in[data-motion^=from-],.data-\[state\=open\]\:animate-in[data-state=open],.data-\[state\=visible\]\:animate-in[data-state=visible]{animation-name:enter;animation-duration:.15s;--tw-enter-opacity: initial;--tw-enter-scale: initial;--tw-enter-rotate: initial;--tw-enter-translate-x: initial;--tw-enter-translate-y: initial}.data-\[motion\^\=to-\]\:animate-out[data-motion^=to-],.data-\[state\=closed\]\:animate-out[data-state=closed],.data-\[state\=hidden\]\:animate-out[data-state=hidden],.data-\[swipe\=end\]\:animate-out[data-swipe=end]{animation-name:exit;animation-duration:.15s;--tw-exit-opacity: initial;--tw-exit-scale: initial;--tw-exit-rotate: initial;--tw-exit-translate-x: initial;--tw-exit-translate-y: initial}.data-\[motion\^\=from-\]\:fade-in[data-motion^=from-]{--tw-enter-opacity: 0}.data-\[motion\^\=to-\]\:fade-out[data-motion^=to-],.data-\[state\=closed\]\:fade-out-0[data-state=closed]{--tw-exit-opacity: 0}.data-\[state\=closed\]\:fade-out-80[data-state=closed]{--tw-exit-opacity: .8}.data-\[state\=hidden\]\:fade-out[data-state=hidden]{--tw-exit-opacity: 0}.data-\[state\=open\]\:fade-in-0[data-state=open],.data-\[state\=visible\]\:fade-in[data-state=visible]{--tw-enter-opacity: 0}.data-\[state\=closed\]\:zoom-out-95[data-state=closed]{--tw-exit-scale: .95}.data-\[state\=open\]\:zoom-in-90[data-state=open]{--tw-enter-scale: .9}.data-\[state\=open\]\:zoom-in-95[data-state=open]{--tw-enter-scale: .95}.data-\[motion\=from-end\]\:slide-in-from-right-52[data-motion=from-end]{--tw-enter-translate-x: 13rem}.data-\[motion\=from-start\]\:slide-in-from-left-52[data-motion=from-start]{--tw-enter-translate-x: -13rem}.data-\[motion\=to-end\]\:slide-out-to-right-52[data-motion=to-end]{--tw-exit-translate-x: 13rem}.data-\[motion\=to-start\]\:slide-out-to-left-52[data-motion=to-start]{--tw-exit-translate-x: -13rem}.data-\[side\=bottom\]\:slide-in-from-top-2[data-side=bottom]{--tw-enter-translate-y: -.5rem}.data-\[side\=left\]\:slide-in-from-right-2[data-side=left]{--tw-enter-translate-x: .5rem}.data-\[side\=right\]\:slide-in-from-left-2[data-side=right]{--tw-enter-translate-x: -.5rem}.data-\[side\=top\]\:slide-in-from-bottom-2[data-side=top]{--tw-enter-translate-y: .5rem}.data-\[state\=closed\]\:slide-out-to-bottom[data-state=closed]{--tw-exit-translate-y: 100%}.data-\[state\=closed\]\:slide-out-to-left[data-state=closed]{--tw-exit-translate-x: -100%}.data-\[state\=closed\]\:slide-out-to-left-1\/2[data-state=closed]{--tw-exit-translate-x: -50%}.data-\[state\=closed\]\:slide-out-to-right[data-state=closed],.data-\[state\=closed\]\:slide-out-to-right-full[data-state=closed]{--tw-exit-translate-x: 100%}.data-\[state\=closed\]\:slide-out-to-top[data-state=closed]{--tw-exit-translate-y: -100%}.data-\[state\=closed\]\:slide-out-to-top-\[48\%\][data-state=closed]{--tw-exit-translate-y: -48%}.data-\[state\=open\]\:slide-in-from-bottom[data-state=open]{--tw-enter-translate-y: 100%}.data-\[state\=open\]\:slide-in-from-left[data-state=open]{--tw-enter-translate-x: -100%}.data-\[state\=open\]\:slide-in-from-left-1\/2[data-state=open]{--tw-enter-translate-x: -50%}.data-\[state\=open\]\:slide-in-from-right[data-state=open]{--tw-enter-translate-x: 100%}.data-\[state\=open\]\:slide-in-from-top[data-state=open]{--tw-enter-translate-y: -100%}.data-\[state\=open\]\:slide-in-from-top-\[48\%\][data-state=open]{--tw-enter-translate-y: -48%}.data-\[state\=open\]\:slide-in-from-top-full[data-state=open]{--tw-enter-translate-y: -100%}.data-\[state\=closed\]\:duration-300[data-state=closed]{animation-duration:.3s}.data-\[state\=open\]\:duration-500[data-state=open]{animation-duration:.5s}.data-\[panel-group-direction\=vertical\]\:after\:left-0[data-panel-group-direction=vertical]:after{content:var(--tw-content);left:0}.data-\[panel-group-direction\=vertical\]\:after\:h-1[data-panel-group-direction=vertical]:after{content:var(--tw-content);height:.25rem}.data-\[panel-group-direction\=vertical\]\:after\:w-full[data-panel-group-direction=vertical]:after{content:var(--tw-content);width:100%}.data-\[panel-group-direction\=vertical\]\:after\:-translate-y-1\/2[data-panel-group-direction=vertical]:after{content:var(--tw-content);--tw-translate-y: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.data-\[panel-group-direction\=vertical\]\:after\:translate-x-0[data-panel-group-direction=vertical]:after{content:var(--tw-content);--tw-translate-x: 0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.data-\[state\=open\]\:hover\:bg-accent:hover[data-state=open]{background-color:var(--accent)}.data-\[state\=open\]\:hover\:bg-sidebar-accent:hover[data-state=open]{background-color:var(--sidebar-accent)}.data-\[state\=open\]\:hover\:text-sidebar-accent-foreground:hover[data-state=open]{color:var(--sidebar-accent-foreground)}.data-\[state\=open\]\:focus\:bg-accent:focus[data-state=open]{background-color:var(--accent)}.group[data-collapsible=offcanvas] .group-data-\[collapsible\=offcanvas\]\:left-\[calc\(var\(--sidebar-width\)\*-1\)\]{left:calc(var(--sidebar-width) * -1)}.group[data-collapsible=offcanvas] .group-data-\[collapsible\=offcanvas\]\:right-\[calc\(var\(--sidebar-width\)\*-1\)\]{right:calc(var(--sidebar-width) * -1)}.group[data-side=left] .group-data-\[side\=left\]\:-right-4{right:-1rem}.group[data-side=right] .group-data-\[side\=right\]\:left-0{left:0}.group[data-collapsible=icon] .group-data-\[collapsible\=icon\]\:-mt-8{margin-top:-2rem}.group[data-collapsible=icon] .group-data-\[collapsible\=icon\]\:hidden{display:none}.group[data-collapsible=icon] .group-data-\[collapsible\=icon\]\:\!size-8{width:2rem!important;height:2rem!important}.group[data-collapsible=icon] .group-data-\[collapsible\=icon\]\:w-\[--sidebar-width-icon\]{width:var(--sidebar-width-icon)}.group[data-collapsible=icon] .group-data-\[collapsible\=icon\]\:w-\[calc\(var\(--sidebar-width-icon\)_\+_theme\(spacing\.4\)\)\]{width:calc(var(--sidebar-width-icon) + 1rem)}.group[data-collapsible=icon] .group-data-\[collapsible\=icon\]\:w-\[calc\(var\(--sidebar-width-icon\)_\+_theme\(spacing\.4\)_\+2px\)\]{width:calc(var(--sidebar-width-icon) + 1rem + 2px)}.group[data-collapsible=offcanvas] .group-data-\[collapsible\=offcanvas\]\:w-0{width:0px}.group[data-collapsible=offcanvas] .group-data-\[collapsible\=offcanvas\]\:translate-x-0{--tw-translate-x: 0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group[data-side=right] .group-data-\[side\=right\]\:rotate-180,.group[data-state=open] .group-data-\[state\=open\]\:rotate-180{--tw-rotate: 180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group[data-collapsible=icon] .group-data-\[collapsible\=icon\]\:overflow-hidden{overflow:hidden}.group[data-variant=floating] .group-data-\[variant\=floating\]\:rounded-lg{border-radius:var(--radius)}.group[data-variant=floating] .group-data-\[variant\=floating\]\:border{border-width:1px}.group[data-side=left] .group-data-\[side\=left\]\:border-r{border-right-width:1px}.group[data-side=right] .group-data-\[side\=right\]\:border-l{border-left-width:1px}.group[data-variant=floating] .group-data-\[variant\=floating\]\:border-sidebar-border{border-color:var(--sidebar-border)}.group[data-collapsible=icon] .group-data-\[collapsible\=icon\]\:\!p-0{padding:0!important}.group[data-collapsible=icon] .group-data-\[collapsible\=icon\]\:\!p-2{padding:.5rem!important}.group[data-collapsible=icon] .group-data-\[collapsible\=icon\]\:opacity-0{opacity:0}.group[data-variant=floating] .group-data-\[variant\=floating\]\:shadow{--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / .1), 0 1px 2px -1px rgb(0 0 0 / .1);--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.group[data-collapsible=offcanvas] .group-data-\[collapsible\=offcanvas\]\:after\:left-full:after{content:var(--tw-content);left:100%}.group[data-collapsible=offcanvas] .group-data-\[collapsible\=offcanvas\]\:hover\:bg-sidebar:hover{background-color:var(--sidebar-background)}.peer\/menu-button[data-size=default]~.peer-data-\[size\=default\]\/menu-button\:top-1\.5{top:.375rem}.peer\/menu-button[data-size=lg]~.peer-data-\[size\=lg\]\/menu-button\:top-2\.5{top:.625rem}.peer\/menu-button[data-size=sm]~.peer-data-\[size\=sm\]\/menu-button\:top-1{top:.25rem}.peer\/menu-button[data-active=true]~.peer-data-\[active\=true\]\/menu-button\:text-sidebar-accent-foreground{color:var(--sidebar-accent-foreground)}.dark\:border-blue-800:is(.dark *){--tw-border-opacity: 1;border-color:rgb(30 64 175 / var(--tw-border-opacity, 1))}.dark\:border-destructive:is(.dark *){border-color:var(--destructive)}.dark\:border-gray-600:is(.dark *){--tw-border-opacity: 1;border-color:rgb(75 85 99 / var(--tw-border-opacity, 1))}.dark\:border-gray-800:is(.dark *){--tw-border-opacity: 1;border-color:rgb(31 41 55 / var(--tw-border-opacity, 1))}.dark\:border-green-800:is(.dark *){--tw-border-opacity: 1;border-color:rgb(22 101 52 / var(--tw-border-opacity, 1))}.dark\:border-orange-800:is(.dark *){--tw-border-opacity: 1;border-color:rgb(154 52 18 / var(--tw-border-opacity, 1))}.dark\:bg-blue-900\/20:is(.dark *){background-color:#1e3a8a33}.dark\:bg-card:is(.dark *){background-color:var(--card)}.dark\:bg-gray-600:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(75 85 99 / var(--tw-bg-opacity, 1))}.dark\:bg-gray-700:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(55 65 81 / var(--tw-bg-opacity, 1))}.dark\:bg-gray-800:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(31 41 55 / var(--tw-bg-opacity, 1))}.dark\:bg-green-900\/20:is(.dark *){background-color:#14532d33}.dark\:bg-orange-950\/20:is(.dark *){background-color:#43140733}.dark\:text-blue-400:is(.dark *){--tw-text-opacity: 1;color:rgb(96 165 250 / var(--tw-text-opacity, 1))}.dark\:text-gray-300:is(.dark *){--tw-text-opacity: 1;color:rgb(209 213 219 / var(--tw-text-opacity, 1))}.dark\:text-gray-400:is(.dark *){--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity, 1))}.dark\:text-green-400:is(.dark *){--tw-text-opacity: 1;color:rgb(74 222 128 / var(--tw-text-opacity, 1))}.dark\:text-orange-200:is(.dark *){--tw-text-opacity: 1;color:rgb(254 215 170 / var(--tw-text-opacity, 1))}.dark\:text-orange-400:is(.dark *){--tw-text-opacity: 1;color:rgb(251 146 60 / var(--tw-text-opacity, 1))}.dark\:text-red-400:is(.dark *){--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity, 1))}.dark\:text-white:is(.dark *){--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.dark\:hover\:bg-gray-800:hover:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(31 41 55 / var(--tw-bg-opacity, 1))}.dark\:hover\:text-gray-200:hover:is(.dark *){--tw-text-opacity: 1;color:rgb(229 231 235 / var(--tw-text-opacity, 1))}.dark\:hover\:text-white:hover:is(.dark *){--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}@media (min-width: 640px){.sm\:bottom-0{bottom:0}.sm\:right-0{right:0}.sm\:top-auto{top:auto}.sm\:mb-2{margin-bottom:.5rem}.sm\:mb-6{margin-bottom:1.5rem}.sm\:mt-0{margin-top:0}.sm\:inline{display:inline}.sm\:flex{display:flex}.sm\:hidden{display:none}.sm\:h-10{height:2.5rem}.sm\:h-20{height:5rem}.sm\:h-48{height:12rem}.sm\:h-5{height:1.25rem}.sm\:w-10{width:2.5rem}.sm\:w-20{width:5rem}.sm\:w-5{width:1.25rem}.sm\:max-w-md{max-width:28rem}.sm\:max-w-sm{max-width:24rem}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:flex-row{flex-direction:row}.sm\:flex-col{flex-direction:column}.sm\:items-center{align-items:center}.sm\:justify-end{justify-content:flex-end}.sm\:gap-2\.5{gap:.625rem}.sm\:space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(.5rem * var(--tw-space-x-reverse));margin-left:calc(.5rem * calc(1 - var(--tw-space-x-reverse)))}.sm\:space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(1rem * var(--tw-space-x-reverse));margin-left:calc(1rem * calc(1 - var(--tw-space-x-reverse)))}.sm\:space-y-0>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(0px * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(0px * var(--tw-space-y-reverse))}.sm\:rounded-lg{border-radius:var(--radius)}.sm\:p-6{padding:1.5rem}.sm\:px-6{padding-left:1.5rem;padding-right:1.5rem}.sm\:py-6{padding-top:1.5rem;padding-bottom:1.5rem}.sm\:text-left{text-align:left}.sm\:text-sm{font-size:.875rem;line-height:1.25rem}.sm\:text-xl{font-size:1.25rem;line-height:1.75rem}.sm\:ring-4{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.data-\[state\=open\]\:sm\:slide-in-from-bottom-full[data-state=open]{--tw-enter-translate-y: 100%}}@media (min-width: 768px){.md\:absolute{position:absolute}.md\:left-auto{left:auto}.md\:right-4{right:1rem}.md\:mb-8{margin-bottom:2rem}.md\:ml-64{margin-left:16rem}.md\:mt-0{margin-top:0}.md\:mt-8{margin-top:2rem}.md\:block{display:block}.md\:flex{display:flex}.md\:hidden{display:none}.md\:h-12{height:3rem}.md\:h-24{height:6rem}.md\:h-4{height:1rem}.md\:h-56{height:14rem}.md\:h-6{height:1.5rem}.md\:w-12{width:3rem}.md\:w-24{width:6rem}.md\:w-4{width:1rem}.md\:w-6{width:1.5rem}.md\:w-80{width:20rem}.md\:w-96{width:24rem}.md\:w-\[var\(--radix-navigation-menu-viewport-width\)\]{width:var(--radix-navigation-menu-viewport-width)}.md\:w-auto{width:auto}.md\:max-w-\[420px\]{max-width:420px}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:flex-row{flex-direction:row}.md\:items-center{align-items:center}.md\:gap-6{gap:1.5rem}.md\:space-y-0>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(0px * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(0px * var(--tw-space-y-reverse))}.md\:px-6{padding-left:1.5rem;padding-right:1.5rem}.md\:py-4{padding-top:1rem;padding-bottom:1rem}.md\:py-8{padding-top:2rem;padding-bottom:2rem}.md\:text-2xl{font-size:1.5rem;line-height:2rem}.md\:text-3xl{font-size:1.875rem;line-height:2.25rem}.md\:text-sm{font-size:.875rem;line-height:1.25rem}.md\:opacity-0{opacity:0}.after\:md\:hidden:after{content:var(--tw-content);display:none}.group:hover .md\:group-hover\:opacity-100{opacity:1}.peer[data-variant=inset]~.md\:peer-data-\[variant\=inset\]\:m-2{margin:.5rem}.peer[data-state=collapsed][data-variant=inset]~.md\:peer-data-\[state\=collapsed\]\:peer-data-\[variant\=inset\]\:ml-2{margin-left:.5rem}.peer[data-variant=inset]~.md\:peer-data-\[variant\=inset\]\:ml-0{margin-left:0}.peer[data-variant=inset]~.md\:peer-data-\[variant\=inset\]\:rounded-xl{border-radius:.75rem}.peer[data-variant=inset]~.md\:peer-data-\[variant\=inset\]\:shadow{--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / .1), 0 1px 2px -1px rgb(0 0 0 / .1);--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}}@media (min-width: 1024px){.lg\:col-span-1{grid-column:span 1 / span 1}.lg\:col-span-2{grid-column:span 2 / span 2}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:text-3xl{font-size:1.875rem;line-height:2.25rem}}@media (min-width: 1280px){.xl\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}.\[\&\:has\(\[aria-selected\]\)\]\:bg-accent:has([aria-selected]){background-color:var(--accent)}.first\:\[\&\:has\(\[aria-selected\]\)\]\:rounded-l-md:has([aria-selected]):first-child{border-top-left-radius:calc(var(--radius) - 2px);border-bottom-left-radius:calc(var(--radius) - 2px)}.last\:\[\&\:has\(\[aria-selected\]\)\]\:rounded-r-md:has([aria-selected]):last-child{border-top-right-radius:calc(var(--radius) - 2px);border-bottom-right-radius:calc(var(--radius) - 2px)}.\[\&\:has\(\[aria-selected\]\.day-range-end\)\]\:rounded-r-md:has([aria-selected].day-range-end){border-top-right-radius:calc(var(--radius) - 2px);border-bottom-right-radius:calc(var(--radius) - 2px)}.\[\&\:has\(\[role\=checkbox\]\)\]\:pr-0:has([role=checkbox]){padding-right:0}.\[\&\>button\]\:hidden>button{display:none}.\[\&\>span\:last-child\]\:truncate>span:last-child{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.\[\&\>span\]\:line-clamp-1>span{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:1}.\[\&\>svg\+div\]\:translate-y-\[-3px\]>svg+div{--tw-translate-y: -3px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.\[\&\>svg\]\:absolute>svg{position:absolute}.\[\&\>svg\]\:left-4>svg{left:1rem}.\[\&\>svg\]\:top-4>svg{top:1rem}.\[\&\>svg\]\:size-4>svg{width:1rem;height:1rem}.\[\&\>svg\]\:h-2\.5>svg{height:.625rem}.\[\&\>svg\]\:h-3>svg{height:.75rem}.\[\&\>svg\]\:h-3\.5>svg{height:.875rem}.\[\&\>svg\]\:w-2\.5>svg{width:.625rem}.\[\&\>svg\]\:w-3>svg{width:.75rem}.\[\&\>svg\]\:w-3\.5>svg{width:.875rem}.\[\&\>svg\]\:shrink-0>svg{flex-shrink:0}.\[\&\>svg\]\:text-destructive>svg{color:var(--destructive)}.\[\&\>svg\]\:text-foreground>svg{color:var(--foreground)}.\[\&\>svg\]\:text-muted-foreground>svg{color:var(--muted-foreground)}.\[\&\>svg\]\:text-sidebar-accent-foreground>svg{color:var(--sidebar-accent-foreground)}.\[\&\>svg\~\*\]\:pl-7>svg~*{padding-left:1.75rem}.\[\&\>tr\]\:last\:border-b-0:last-child>tr{border-bottom-width:0px}.\[\&\[data-panel-group-direction\=vertical\]\>div\]\:rotate-90[data-panel-group-direction=vertical]>div{--tw-rotate: 90deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.\[\&\[data-state\=open\]\>svg\]\:rotate-180[data-state=open]>svg{--tw-rotate: 180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.\[\&_\.recharts-cartesian-axis-tick_text\]\:fill-muted-foreground .recharts-cartesian-axis-tick text{fill:var(--muted-foreground)}.\[\&_\.recharts-curve\.recharts-tooltip-cursor\]\:stroke-border .recharts-curve.recharts-tooltip-cursor{stroke:var(--border)}.\[\&_\.recharts-dot\[stroke\=\'\#fff\'\]\]\:stroke-transparent .recharts-dot[stroke="#fff"]{stroke:transparent}.\[\&_\.recharts-layer\]\:outline-none .recharts-layer{outline:2px solid transparent;outline-offset:2px}.\[\&_\.recharts-polar-grid_\[stroke\=\'\#ccc\'\]\]\:stroke-border .recharts-polar-grid [stroke="#ccc"]{stroke:var(--border)}.\[\&_\.recharts-radial-bar-background-sector\]\:fill-muted .recharts-radial-bar-background-sector,.\[\&_\.recharts-rectangle\.recharts-tooltip-cursor\]\:fill-muted .recharts-rectangle.recharts-tooltip-cursor{fill:var(--muted)}.\[\&_\.recharts-reference-line_\[stroke\=\'\#ccc\'\]\]\:stroke-border .recharts-reference-line [stroke="#ccc"]{stroke:var(--border)}.\[\&_\.recharts-sector\[stroke\=\'\#fff\'\]\]\:stroke-transparent .recharts-sector[stroke="#fff"]{stroke:transparent}.\[\&_\.recharts-sector\]\:outline-none .recharts-sector,.\[\&_\.recharts-surface\]\:outline-none .recharts-surface{outline:2px solid transparent;outline-offset:2px}.\[\&_\[cmdk-group-heading\]\]\:px-2 [cmdk-group-heading]{padding-left:.5rem;padding-right:.5rem}.\[\&_\[cmdk-group-heading\]\]\:py-1\.5 [cmdk-group-heading]{padding-top:.375rem;padding-bottom:.375rem}.\[\&_\[cmdk-group-heading\]\]\:text-xs [cmdk-group-heading]{font-size:.75rem;line-height:1rem}.\[\&_\[cmdk-group-heading\]\]\:font-medium [cmdk-group-heading]{font-weight:500}.\[\&_\[cmdk-group-heading\]\]\:text-muted-foreground [cmdk-group-heading]{color:var(--muted-foreground)}.\[\&_\[cmdk-group\]\:not\(\[hidden\]\)_\~\[cmdk-group\]\]\:pt-0 [cmdk-group]:not([hidden])~[cmdk-group]{padding-top:0}.\[\&_\[cmdk-group\]\]\:px-2 [cmdk-group]{padding-left:.5rem;padding-right:.5rem}.\[\&_\[cmdk-input-wrapper\]_svg\]\:h-5 [cmdk-input-wrapper] svg{height:1.25rem}.\[\&_\[cmdk-input-wrapper\]_svg\]\:w-5 [cmdk-input-wrapper] svg{width:1.25rem}.\[\&_\[cmdk-input\]\]\:h-12 [cmdk-input]{height:3rem}.\[\&_\[cmdk-item\]\]\:px-2 [cmdk-item]{padding-left:.5rem;padding-right:.5rem}.\[\&_\[cmdk-item\]\]\:py-3 [cmdk-item]{padding-top:.75rem;padding-bottom:.75rem}.\[\&_\[cmdk-item\]_svg\]\:h-5 [cmdk-item] svg{height:1.25rem}.\[\&_\[cmdk-item\]_svg\]\:w-5 [cmdk-item] svg{width:1.25rem}.\[\&_p\]\:leading-relaxed p{line-height:1.625}.\[\&_svg\]\:pointer-events-none svg{pointer-events:none}.\[\&_svg\]\:size-4 svg{width:1rem;height:1rem}.\[\&_svg\]\:shrink-0 svg{flex-shrink:0}.\[\&_tr\:last-child\]\:border-0 tr:last-child{border-width:0px}.\[\&_tr\]\:border-b tr{border-bottom-width:1px}[data-side=left][data-collapsible=offcanvas] .\[\[data-side\=left\]\[data-collapsible\=offcanvas\]_\&\]\:-right-2{right:-.5rem}[data-side=left][data-state=collapsed] .\[\[data-side\=left\]\[data-state\=collapsed\]_\&\]\:cursor-e-resize{cursor:e-resize}[data-side=left] .\[\[data-side\=left\]_\&\]\:cursor-w-resize{cursor:w-resize}[data-side=right][data-collapsible=offcanvas] .\[\[data-side\=right\]\[data-collapsible\=offcanvas\]_\&\]\:-left-2{left:-.5rem}[data-side=right][data-state=collapsed] .\[\[data-side\=right\]\[data-state\=collapsed\]_\&\]\:cursor-w-resize{cursor:w-resize}[data-side=right] .\[\[data-side\=right\]_\&\]\:cursor-e-resize{cursor:e-resize}
|
dist/public/create_icon.js
ADDED
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// Simple script to create PWA icons
|
2 |
+
const canvas = document.createElement('canvas');
|
3 |
+
const ctx = canvas.getContext('2d');
|
4 |
+
|
5 |
+
function createIcon(size) {
|
6 |
+
canvas.width = size;
|
7 |
+
canvas.height = size;
|
8 |
+
|
9 |
+
// Background
|
10 |
+
ctx.fillStyle = '#0d6efd';
|
11 |
+
ctx.fillRect(0, 0, size, size);
|
12 |
+
|
13 |
+
// Audio bars
|
14 |
+
ctx.fillStyle = 'white';
|
15 |
+
const barWidth = size * 0.06;
|
16 |
+
const spacing = size * 0.08;
|
17 |
+
const baseY = size * 0.35;
|
18 |
+
|
19 |
+
// Bar heights and positions
|
20 |
+
const bars = [
|
21 |
+
{ height: size * 0.3, offset: 0 },
|
22 |
+
{ height: size * 0.45, offset: -size * 0.075 },
|
23 |
+
{ height: size * 0.38, offset: -size * 0.04 },
|
24 |
+
{ height: size * 0.3, offset: 0 }
|
25 |
+
];
|
26 |
+
|
27 |
+
let x = size * 0.25;
|
28 |
+
bars.forEach(bar => {
|
29 |
+
ctx.fillRect(x, baseY + bar.offset, barWidth, bar.height);
|
30 |
+
x += barWidth + spacing;
|
31 |
+
});
|
32 |
+
|
33 |
+
// Text
|
34 |
+
ctx.fillStyle = 'white';
|
35 |
+
ctx.font = `bold ${size * 0.09}px Arial`;
|
36 |
+
ctx.textAlign = 'center';
|
37 |
+
ctx.fillText('VELIN', size / 2, size * 0.85);
|
38 |
+
|
39 |
+
return canvas.toDataURL('image/png');
|
40 |
+
}
|
41 |
+
|
42 |
+
// Create icons
|
43 |
+
const icon192 = createIcon(192);
|
44 |
+
const icon512 = createIcon(512);
|
45 |
+
|
46 |
+
console.log('Icons created');
|
dist/public/generate_icons.html
ADDED
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!DOCTYPE html>
|
2 |
+
<html>
|
3 |
+
<head>
|
4 |
+
<title>Generate PWA Icons</title>
|
5 |
+
</head>
|
6 |
+
<body>
|
7 |
+
<canvas id="canvas192" width="192" height="192" style="border: 1px solid #000; margin: 10px;"></canvas>
|
8 |
+
<canvas id="canvas512" width="512" height="512" style="border: 1px solid #000; margin: 10px;"></canvas>
|
9 |
+
|
10 |
+
<script>
|
11 |
+
function drawIcon(canvas, size) {
|
12 |
+
const ctx = canvas.getContext('2d');
|
13 |
+
|
14 |
+
// Background
|
15 |
+
ctx.fillStyle = '#0d6efd';
|
16 |
+
ctx.fillRect(0, 0, size, size);
|
17 |
+
|
18 |
+
// Border radius effect (simplified)
|
19 |
+
ctx.globalCompositeOperation = 'destination-in';
|
20 |
+
ctx.beginPath();
|
21 |
+
ctx.roundRect(0, 0, size, size, size * 0.125);
|
22 |
+
ctx.fill();
|
23 |
+
ctx.globalCompositeOperation = 'source-over';
|
24 |
+
|
25 |
+
// Audio bars
|
26 |
+
ctx.fillStyle = 'white';
|
27 |
+
const barWidth = size * 0.06;
|
28 |
+
const spacing = size * 0.08;
|
29 |
+
const baseY = size * 0.35;
|
30 |
+
|
31 |
+
// Bar 1
|
32 |
+
const x1 = size * 0.25;
|
33 |
+
const h1 = size * 0.3;
|
34 |
+
ctx.fillRect(x1, baseY, barWidth, h1);
|
35 |
+
|
36 |
+
// Bar 2
|
37 |
+
const x2 = x1 + barWidth + spacing;
|
38 |
+
const h2 = size * 0.45;
|
39 |
+
ctx.fillRect(x2, baseY - size * 0.075, barWidth, h2);
|
40 |
+
|
41 |
+
// Bar 3
|
42 |
+
const x3 = x2 + barWidth + spacing;
|
43 |
+
const h3 = size * 0.38;
|
44 |
+
ctx.fillRect(x3, baseY - size * 0.04, barWidth, h3);
|
45 |
+
|
46 |
+
// Bar 4
|
47 |
+
const x4 = x3 + barWidth + spacing;
|
48 |
+
const h4 = size * 0.3;
|
49 |
+
ctx.fillRect(x4, baseY, barWidth, h4);
|
50 |
+
|
51 |
+
// Text
|
52 |
+
ctx.fillStyle = 'white';
|
53 |
+
ctx.font = `bold ${size * 0.08}px Arial`;
|
54 |
+
ctx.textAlign = 'center';
|
55 |
+
ctx.fillText('VELIN', size / 2, size * 0.85);
|
56 |
+
}
|
57 |
+
|
58 |
+
drawIcon(document.getElementById('canvas192'), 192);
|
59 |
+
drawIcon(document.getElementById('canvas512'), 512);
|
60 |
+
|
61 |
+
// Convert to downloads
|
62 |
+
setTimeout(() => {
|
63 |
+
const canvas192 = document.getElementById('canvas192');
|
64 |
+
const canvas512 = document.getElementById('canvas512');
|
65 |
+
|
66 |
+
const link192 = document.createElement('a');
|
67 |
+
link192.download = 'icon-192.png';
|
68 |
+
link192.href = canvas192.toDataURL();
|
69 |
+
link192.click();
|
70 |
+
|
71 |
+
setTimeout(() => {
|
72 |
+
const link512 = document.createElement('a');
|
73 |
+
link512.download = 'icon-512.png';
|
74 |
+
link512.href = canvas512.toDataURL();
|
75 |
+
link512.click();
|
76 |
+
}, 1000);
|
77 |
+
}, 1000);
|
78 |
+
</script>
|
79 |
+
</body>
|
80 |
+
</html>
|
dist/public/icon-192.png
ADDED
![]() |
dist/public/icon-192.svg
ADDED
|
dist/public/icon-512.png
ADDED
![]() |
dist/public/icon-512.svg
ADDED
|
dist/public/icon.svg
ADDED
|
dist/public/index.html
ADDED
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!DOCTYPE html>
|
2 |
+
<html lang="en">
|
3 |
+
<head>
|
4 |
+
<meta charset="UTF-8" />
|
5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
|
6 |
+
<title>VELIN - Podcast Player</title>
|
7 |
+
|
8 |
+
<!-- PWA Manifest -->
|
9 |
+
<link rel="manifest" href="/manifest.json" />
|
10 |
+
|
11 |
+
<!-- PWA Meta Tags -->
|
12 |
+
<meta name="theme-color" content="#f97316" />
|
13 |
+
<meta name="description" content="Modern podcast streaming and discovery platform with audio waveform interface" />
|
14 |
+
|
15 |
+
<!-- Apple Touch Icon -->
|
16 |
+
<link rel="apple-touch-icon" href="/waveform-icon.svg" />
|
17 |
+
<link rel="icon" type="image/svg+xml" href="/waveform-icon.svg" />
|
18 |
+
|
19 |
+
<!-- Apple Web App -->
|
20 |
+
<meta name="apple-mobile-web-app-capable" content="yes" />
|
21 |
+
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
|
22 |
+
<meta name="apple-mobile-web-app-title" content="VELIN" />
|
23 |
+
<script type="module" crossorigin src="/assets/index-Bl0SxKIt.js"></script>
|
24 |
+
<link rel="stylesheet" crossorigin href="/assets/index-ZmZ09n2-.css">
|
25 |
+
</head>
|
26 |
+
<body>
|
27 |
+
<div id="root"></div>
|
28 |
+
|
29 |
+
<!-- Service Worker Registration -->
|
30 |
+
<script>
|
31 |
+
if ('serviceWorker' in navigator) {
|
32 |
+
window.addEventListener('load', () => {
|
33 |
+
navigator.serviceWorker.register('/sw.js')
|
34 |
+
.then((registration) => {
|
35 |
+
console.log('SW registered: ', registration);
|
36 |
+
})
|
37 |
+
.catch((registrationError) => {
|
38 |
+
console.log('SW registration failed: ', registrationError);
|
39 |
+
});
|
40 |
+
});
|
41 |
+
}
|
42 |
+
</script>
|
43 |
+
|
44 |
+
<!-- This is a replit script which adds a banner on the top of the page when opened in development mode outside the replit environment -->
|
45 |
+
<script type="text/javascript" src="https://replit.com/public/js/replit-dev-banner.js"></script>
|
46 |
+
</body>
|
47 |
+
</html>
|
dist/public/manifest.json
ADDED
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"name": "VELIN - Podcast Player",
|
3 |
+
"short_name": "VELIN",
|
4 |
+
"description": "Modern podcast streaming and discovery platform",
|
5 |
+
"start_url": "/",
|
6 |
+
"display": "standalone",
|
7 |
+
"background_color": "#ffffff",
|
8 |
+
"theme_color": "#f97316",
|
9 |
+
"orientation": "portrait-primary",
|
10 |
+
"scope": "/",
|
11 |
+
"lang": "en",
|
12 |
+
"categories": ["entertainment", "music"],
|
13 |
+
"icons": [
|
14 |
+
{
|
15 |
+
"src": "/waveform-icon.svg",
|
16 |
+
"sizes": "72x72 96x96 128x128 144x144 152x152 192x192 384x384 512x512",
|
17 |
+
"type": "image/svg+xml",
|
18 |
+
"purpose": "any maskable"
|
19 |
+
},
|
20 |
+
{
|
21 |
+
"src": "/waveform-icon.svg",
|
22 |
+
"sizes": "192x192",
|
23 |
+
"type": "image/svg+xml",
|
24 |
+
"purpose": "any"
|
25 |
+
},
|
26 |
+
{
|
27 |
+
"src": "/waveform-icon.svg",
|
28 |
+
"sizes": "512x512",
|
29 |
+
"type": "image/svg+xml",
|
30 |
+
"purpose": "any"
|
31 |
+
}
|
32 |
+
]
|
33 |
+
}
|
dist/public/offline.html
ADDED
@@ -0,0 +1,89 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!DOCTYPE html>
|
2 |
+
<html lang="en">
|
3 |
+
<head>
|
4 |
+
<meta charset="UTF-8">
|
5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6 |
+
<title>VELIN - Offline</title>
|
7 |
+
<style>
|
8 |
+
body {
|
9 |
+
font-family: 'Roboto', Arial, sans-serif;
|
10 |
+
background: #121212;
|
11 |
+
color: #ffffff;
|
12 |
+
margin: 0;
|
13 |
+
padding: 0;
|
14 |
+
display: flex;
|
15 |
+
flex-direction: column;
|
16 |
+
align-items: center;
|
17 |
+
justify-content: center;
|
18 |
+
min-height: 100vh;
|
19 |
+
text-align: center;
|
20 |
+
}
|
21 |
+
.container {
|
22 |
+
max-width: 400px;
|
23 |
+
padding: 2rem;
|
24 |
+
}
|
25 |
+
.logo {
|
26 |
+
font-size: 2rem;
|
27 |
+
font-weight: bold;
|
28 |
+
margin-bottom: 1rem;
|
29 |
+
color: #0d6efd;
|
30 |
+
}
|
31 |
+
.message {
|
32 |
+
font-size: 1.2rem;
|
33 |
+
margin-bottom: 1rem;
|
34 |
+
}
|
35 |
+
.description {
|
36 |
+
color: #cccccc;
|
37 |
+
margin-bottom: 2rem;
|
38 |
+
}
|
39 |
+
.button {
|
40 |
+
background: #0d6efd;
|
41 |
+
color: white;
|
42 |
+
border: none;
|
43 |
+
padding: 0.75rem 1.5rem;
|
44 |
+
border-radius: 0.5rem;
|
45 |
+
font-size: 1rem;
|
46 |
+
cursor: pointer;
|
47 |
+
text-decoration: none;
|
48 |
+
display: inline-block;
|
49 |
+
}
|
50 |
+
.button:hover {
|
51 |
+
background: #0b5ed7;
|
52 |
+
}
|
53 |
+
.offline-icon {
|
54 |
+
width: 64px;
|
55 |
+
height: 64px;
|
56 |
+
margin: 0 auto 2rem;
|
57 |
+
opacity: 0.5;
|
58 |
+
}
|
59 |
+
</style>
|
60 |
+
</head>
|
61 |
+
<body>
|
62 |
+
<div class="container">
|
63 |
+
<div class="offline-icon">
|
64 |
+
<svg viewBox="0 0 24 24" fill="currentColor">
|
65 |
+
<path d="M23 12l-2.44-2.78.34-3.68-3.61-.82-1.89-3.18L12 3 8.6 1.54 6.71 4.72l-3.61.81.34 3.68L1 12l2.44 2.78-.34 3.68 3.61.82 1.89 3.18L12 21l3.4 1.46 1.89-3.18 3.61-.81-.34-3.68L23 12zM13 17h-2v-6h2v6zm0-8h-2V7h2v2z"/>
|
66 |
+
</svg>
|
67 |
+
</div>
|
68 |
+
|
69 |
+
<div class="logo">VELIN</div>
|
70 |
+
|
71 |
+
<div class="message">You're offline</div>
|
72 |
+
|
73 |
+
<div class="description">
|
74 |
+
No internet connection detected. You can still listen to your downloaded podcasts in the Downloads section.
|
75 |
+
</div>
|
76 |
+
|
77 |
+
<a href="/" class="button" onclick="window.location.reload()">
|
78 |
+
Try Again
|
79 |
+
</a>
|
80 |
+
</div>
|
81 |
+
|
82 |
+
<script>
|
83 |
+
// Check for connection and reload when back online
|
84 |
+
window.addEventListener('online', () => {
|
85 |
+
window.location.reload();
|
86 |
+
});
|
87 |
+
</script>
|
88 |
+
</body>
|
89 |
+
</html>
|
dist/public/register-sw.js
ADDED
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// Service Worker Registration Script
|
2 |
+
// This ensures the service worker is only registered once
|
3 |
+
|
4 |
+
if ('serviceWorker' in navigator) {
|
5 |
+
window.addEventListener('load', () => {
|
6 |
+
navigator.serviceWorker.register('/sw.js', {
|
7 |
+
scope: '/'
|
8 |
+
})
|
9 |
+
.then(registration => {
|
10 |
+
console.log('SW registered: ', registration);
|
11 |
+
|
12 |
+
// Handle updates
|
13 |
+
registration.addEventListener('updatefound', () => {
|
14 |
+
const newWorker = registration.installing;
|
15 |
+
if (newWorker) {
|
16 |
+
newWorker.addEventListener('statechange', () => {
|
17 |
+
if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
|
18 |
+
// New service worker available
|
19 |
+
console.log('New service worker available');
|
20 |
+
}
|
21 |
+
});
|
22 |
+
}
|
23 |
+
});
|
24 |
+
})
|
25 |
+
.catch(registrationError => {
|
26 |
+
console.log('SW registration failed: ', registrationError);
|
27 |
+
});
|
28 |
+
});
|
29 |
+
}
|
dist/public/sw.js
ADDED
@@ -0,0 +1,542 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// Service Worker for VELIN Podcast Player
|
2 |
+
// Handles offline audio caching and playback
|
3 |
+
|
4 |
+
const CACHE_NAME = 'velin-audio-cache-v2';
|
5 |
+
const STATIC_CACHE_NAME = 'velin-static-v2';
|
6 |
+
const THUMBNAIL_CACHE_NAME = 'velin-thumbnails-v2';
|
7 |
+
|
8 |
+
// Static files to cache for offline functionality
|
9 |
+
const STATIC_FILES = [
|
10 |
+
'/',
|
11 |
+
'/manifest.json',
|
12 |
+
'/icon-192.png',
|
13 |
+
'/icon-512.png',
|
14 |
+
'/icon-192.svg',
|
15 |
+
'/icon-512.svg'
|
16 |
+
];
|
17 |
+
|
18 |
+
// Install event - cache static files and discover dynamic assets
|
19 |
+
self.addEventListener('install', event => {
|
20 |
+
console.log('VELIN Service Worker installed');
|
21 |
+
|
22 |
+
event.waitUntil(
|
23 |
+
cacheStaticFiles()
|
24 |
+
.then(() => {
|
25 |
+
console.log('All static files cached successfully');
|
26 |
+
})
|
27 |
+
.catch(error => {
|
28 |
+
console.error('Failed to cache static files:', error);
|
29 |
+
})
|
30 |
+
);
|
31 |
+
|
32 |
+
self.skipWaiting();
|
33 |
+
});
|
34 |
+
|
35 |
+
// Cache static files and discover dynamic assets
|
36 |
+
async function cacheStaticFiles() {
|
37 |
+
try {
|
38 |
+
const cache = await caches.open(STATIC_CACHE_NAME);
|
39 |
+
|
40 |
+
// First, cache the main HTML page to discover dynamic assets
|
41 |
+
const indexResponse = await fetch('/');
|
42 |
+
if (indexResponse.ok) {
|
43 |
+
await cache.put('/', indexResponse.clone());
|
44 |
+
|
45 |
+
// Parse HTML to find JS and CSS files
|
46 |
+
const htmlText = await indexResponse.text();
|
47 |
+
const dynamicAssets = extractAssetsFromHTML(htmlText);
|
48 |
+
|
49 |
+
console.log('Found dynamic assets:', dynamicAssets);
|
50 |
+
|
51 |
+
// Cache all discovered assets along with static files
|
52 |
+
const allFilesToCache = [...STATIC_FILES.filter(file => file !== '/'), ...dynamicAssets];
|
53 |
+
|
54 |
+
// Cache each file individually to handle failures gracefully
|
55 |
+
await Promise.allSettled(
|
56 |
+
allFilesToCache.map(async (file) => {
|
57 |
+
try {
|
58 |
+
const response = await fetch(file);
|
59 |
+
if (response.ok) {
|
60 |
+
await cache.put(file, response);
|
61 |
+
console.log('Cached:', file);
|
62 |
+
} else {
|
63 |
+
console.warn('Failed to fetch:', file, response.status);
|
64 |
+
}
|
65 |
+
} catch (error) {
|
66 |
+
console.warn('Error caching:', file, error);
|
67 |
+
}
|
68 |
+
})
|
69 |
+
);
|
70 |
+
}
|
71 |
+
} catch (error) {
|
72 |
+
console.error('Error in cacheStaticFiles:', error);
|
73 |
+
// Fallback: try to cache just the static files
|
74 |
+
const cache = await caches.open(STATIC_CACHE_NAME);
|
75 |
+
await Promise.allSettled(
|
76 |
+
STATIC_FILES.map(async (file) => {
|
77 |
+
try {
|
78 |
+
const response = await fetch(file);
|
79 |
+
if (response.ok) {
|
80 |
+
await cache.put(file, response);
|
81 |
+
}
|
82 |
+
} catch (error) {
|
83 |
+
console.warn('Fallback cache error for:', file);
|
84 |
+
}
|
85 |
+
})
|
86 |
+
);
|
87 |
+
}
|
88 |
+
}
|
89 |
+
|
90 |
+
// Extract JS and CSS files from HTML
|
91 |
+
function extractAssetsFromHTML(html) {
|
92 |
+
const assets = [];
|
93 |
+
|
94 |
+
// Find script tags with src
|
95 |
+
const scriptMatches = html.matchAll(/<script[^>]+src="([^"]+)"/g);
|
96 |
+
for (const match of scriptMatches) {
|
97 |
+
if (match[1] && !match[1].startsWith('http') && !match[1].includes('replit')) {
|
98 |
+
assets.push(match[1]);
|
99 |
+
}
|
100 |
+
}
|
101 |
+
|
102 |
+
// Find link tags with stylesheet
|
103 |
+
const linkMatches = html.matchAll(/<link[^>]+href="([^"]+)"[^>]*rel="stylesheet"/g);
|
104 |
+
for (const match of linkMatches) {
|
105 |
+
if (match[1] && !match[1].startsWith('http')) {
|
106 |
+
assets.push(match[1]);
|
107 |
+
}
|
108 |
+
}
|
109 |
+
|
110 |
+
return assets;
|
111 |
+
}
|
112 |
+
|
113 |
+
// Activate event - claim all clients and clean old caches
|
114 |
+
self.addEventListener('activate', event => {
|
115 |
+
console.log('VELIN Service Worker activated');
|
116 |
+
|
117 |
+
event.waitUntil(
|
118 |
+
Promise.all([
|
119 |
+
self.clients.claim(),
|
120 |
+
caches.keys().then(cacheNames => {
|
121 |
+
return Promise.all(
|
122 |
+
cacheNames.map(cacheName => {
|
123 |
+
// Keep current caches, remove old versions
|
124 |
+
if (cacheName !== CACHE_NAME &&
|
125 |
+
cacheName !== STATIC_CACHE_NAME &&
|
126 |
+
cacheName !== THUMBNAIL_CACHE_NAME) {
|
127 |
+
console.log('Deleting old cache:', cacheName);
|
128 |
+
return caches.delete(cacheName);
|
129 |
+
}
|
130 |
+
})
|
131 |
+
);
|
132 |
+
})
|
133 |
+
])
|
134 |
+
);
|
135 |
+
});
|
136 |
+
|
137 |
+
// Fetch event - handle caching and serving
|
138 |
+
self.addEventListener('fetch', event => {
|
139 |
+
const url = new URL(event.request.url);
|
140 |
+
|
141 |
+
// Handle audio files (.mp3, .m4a, .webm, .ogg)
|
142 |
+
if (isAudioFile(url.pathname) || isAudioStream(url.href)) {
|
143 |
+
event.respondWith(handleAudioRequest(event.request));
|
144 |
+
return;
|
145 |
+
}
|
146 |
+
|
147 |
+
// Handle API requests for audio downloads
|
148 |
+
if (url.pathname.startsWith('/api/download-audio/')) {
|
149 |
+
event.respondWith(handleAudioDownload(event.request));
|
150 |
+
return;
|
151 |
+
}
|
152 |
+
|
153 |
+
// Handle podcast stream requests
|
154 |
+
if (url.hostname === 'backendmix.vercel.app' && url.pathname.startsWith('/streams/')) {
|
155 |
+
event.respondWith(handleStreamRequest(event.request));
|
156 |
+
return;
|
157 |
+
}
|
158 |
+
|
159 |
+
// Handle thumbnail images
|
160 |
+
if (isThumbnailImage(url.href)) {
|
161 |
+
event.respondWith(handleThumbnailRequest(event.request));
|
162 |
+
return;
|
163 |
+
}
|
164 |
+
|
165 |
+
// Handle static files and navigation
|
166 |
+
if (event.request.method === 'GET') {
|
167 |
+
event.respondWith(handleStaticRequest(event.request));
|
168 |
+
return;
|
169 |
+
}
|
170 |
+
|
171 |
+
// Default handling for other requests
|
172 |
+
event.respondWith(fetch(event.request));
|
173 |
+
});
|
174 |
+
|
175 |
+
// Check if URL is an audio file
|
176 |
+
function isAudioFile(pathname) {
|
177 |
+
const audioExtensions = ['.mp3', '.m4a', '.webm', '.ogg', '.wav', '.aac'];
|
178 |
+
return audioExtensions.some(ext => pathname.endsWith(ext));
|
179 |
+
}
|
180 |
+
|
181 |
+
// Check if URL is an audio stream
|
182 |
+
function isAudioStream(url) {
|
183 |
+
// YouTube audio streams and other streaming services
|
184 |
+
return (
|
185 |
+
url.includes('googlevideo.com') ||
|
186 |
+
url.includes('youtube.com') ||
|
187 |
+
url.includes('ytimg.com') ||
|
188 |
+
(url.includes('mime=audio') || url.includes('type=audio'))
|
189 |
+
);
|
190 |
+
}
|
191 |
+
|
192 |
+
// Check if URL is a thumbnail image
|
193 |
+
function isThumbnailImage(url) {
|
194 |
+
return (
|
195 |
+
url.includes('ytimg.com') ||
|
196 |
+
url.includes('ggpht.com') ||
|
197 |
+
url.includes('youtube.com/vi/') ||
|
198 |
+
(url.includes('thumbnail') && (url.includes('.jpg') || url.includes('.webp') || url.includes('.png')))
|
199 |
+
);
|
200 |
+
}
|
201 |
+
|
202 |
+
// Handle thumbnail requests
|
203 |
+
async function handleThumbnailRequest(request) {
|
204 |
+
try {
|
205 |
+
const cache = await caches.open(THUMBNAIL_CACHE_NAME);
|
206 |
+
const cachedResponse = await cache.match(request);
|
207 |
+
|
208 |
+
if (cachedResponse) {
|
209 |
+
console.log('Serving thumbnail from cache:', request.url);
|
210 |
+
return cachedResponse;
|
211 |
+
}
|
212 |
+
|
213 |
+
// Fetch from network and cache
|
214 |
+
const networkResponse = await fetch(request);
|
215 |
+
|
216 |
+
if (networkResponse.ok) {
|
217 |
+
const responseClone = networkResponse.clone();
|
218 |
+
await cache.put(request, responseClone);
|
219 |
+
console.log('Thumbnail cached successfully:', request.url);
|
220 |
+
return networkResponse;
|
221 |
+
}
|
222 |
+
|
223 |
+
return networkResponse;
|
224 |
+
} catch (error) {
|
225 |
+
console.error('Error handling thumbnail request:', error);
|
226 |
+
|
227 |
+
// Return a fallback thumbnail when offline
|
228 |
+
const fallbackSvg = `
|
229 |
+
<svg viewBox="0 0 320 180" xmlns="http://www.w3.org/2000/svg">
|
230 |
+
<rect width="320" height="180" fill="#4B5563"/>
|
231 |
+
<g transform="translate(160, 90)">
|
232 |
+
<circle r="24" fill="#6366F1" opacity="0.2"/>
|
233 |
+
<path d="M-8 -6 L8 0 L-8 6 Z" fill="#6366F1"/>
|
234 |
+
</g>
|
235 |
+
<text x="160" y="135" text-anchor="middle" fill="#D1D5DB" font-family="sans-serif" font-size="12" font-weight="500">
|
236 |
+
VELIN
|
237 |
+
</text>
|
238 |
+
</svg>
|
239 |
+
`;
|
240 |
+
|
241 |
+
return new Response(fallbackSvg, {
|
242 |
+
headers: {
|
243 |
+
'Content-Type': 'image/svg+xml',
|
244 |
+
'Cache-Control': 'no-cache'
|
245 |
+
}
|
246 |
+
});
|
247 |
+
}
|
248 |
+
}
|
249 |
+
|
250 |
+
// Handle static file requests (for offline app functionality)
|
251 |
+
async function handleStaticRequest(request) {
|
252 |
+
const url = new URL(request.url);
|
253 |
+
|
254 |
+
try {
|
255 |
+
// For navigation requests (page loads), handle specially for SPA
|
256 |
+
if (request.mode === 'navigate' ||
|
257 |
+
(request.destination === 'document' && request.method === 'GET')) {
|
258 |
+
|
259 |
+
// Try network first for navigation
|
260 |
+
try {
|
261 |
+
const networkResponse = await fetch(request);
|
262 |
+
|
263 |
+
if (networkResponse.ok) {
|
264 |
+
// Cache successful page loads
|
265 |
+
const cache = await caches.open(STATIC_CACHE_NAME);
|
266 |
+
const responseClone = networkResponse.clone();
|
267 |
+
await cache.put(request, responseClone);
|
268 |
+
console.log('Cached page from network:', request.url);
|
269 |
+
return networkResponse;
|
270 |
+
}
|
271 |
+
} catch (networkError) {
|
272 |
+
console.log('Network failed for navigation, using cache fallback');
|
273 |
+
}
|
274 |
+
|
275 |
+
// Network failed or offline - serve from cache
|
276 |
+
const cache = await caches.open(STATIC_CACHE_NAME);
|
277 |
+
|
278 |
+
// For SPA routing, always serve the main index.html for any route
|
279 |
+
const cachedResponse = await cache.match('/');
|
280 |
+
|
281 |
+
if (cachedResponse) {
|
282 |
+
console.log('Serving SPA fallback from cache for:', request.url);
|
283 |
+
return cachedResponse;
|
284 |
+
}
|
285 |
+
|
286 |
+
// No cached page available
|
287 |
+
console.error('No cached page available for offline access');
|
288 |
+
return new Response(`
|
289 |
+
<!DOCTYPE html>
|
290 |
+
<html>
|
291 |
+
<head>
|
292 |
+
<title>VELIN - Offline</title>
|
293 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
294 |
+
<style>
|
295 |
+
body {
|
296 |
+
font-family: system-ui, sans-serif;
|
297 |
+
text-align: center;
|
298 |
+
padding: 2rem;
|
299 |
+
background: #121212;
|
300 |
+
color: #ffffff;
|
301 |
+
margin: 0;
|
302 |
+
}
|
303 |
+
.container { max-width: 400px; margin: 0 auto; padding-top: 20vh; }
|
304 |
+
.icon { font-size: 4rem; margin-bottom: 1rem; }
|
305 |
+
h1 { color: #6366f1; margin-bottom: 1rem; }
|
306 |
+
p { color: #9ca3af; line-height: 1.5; }
|
307 |
+
</style>
|
308 |
+
</head>
|
309 |
+
<body>
|
310 |
+
<div class="container">
|
311 |
+
<div class="icon">📱</div>
|
312 |
+
<h1>VELIN</h1>
|
313 |
+
<p>You're offline and the app isn't cached yet.<br>Please connect to the internet and reload the page.</p>
|
314 |
+
</div>
|
315 |
+
</body>
|
316 |
+
</html>
|
317 |
+
`, {
|
318 |
+
headers: { 'Content-Type': 'text/html' },
|
319 |
+
status: 200
|
320 |
+
});
|
321 |
+
}
|
322 |
+
|
323 |
+
// For static resources (JS, CSS, images, etc.)
|
324 |
+
try {
|
325 |
+
const networkResponse = await fetch(request);
|
326 |
+
|
327 |
+
// Cache successful responses for static resources
|
328 |
+
if (networkResponse.ok && (
|
329 |
+
request.destination === 'script' ||
|
330 |
+
request.destination === 'style' ||
|
331 |
+
request.destination === 'image' ||
|
332 |
+
url.pathname.includes('.js') ||
|
333 |
+
url.pathname.includes('.css') ||
|
334 |
+
url.pathname.includes('.png') ||
|
335 |
+
url.pathname.includes('.svg') ||
|
336 |
+
url.pathname.includes('/assets/')
|
337 |
+
)) {
|
338 |
+
const cache = await caches.open(STATIC_CACHE_NAME);
|
339 |
+
const responseClone = networkResponse.clone();
|
340 |
+
await cache.put(request, responseClone);
|
341 |
+
console.log('Cached static resource:', request.url);
|
342 |
+
}
|
343 |
+
|
344 |
+
return networkResponse;
|
345 |
+
} catch (networkError) {
|
346 |
+
console.log('Network failed for static resource, trying cache:', request.url);
|
347 |
+
|
348 |
+
// Network failed, try cache as fallback
|
349 |
+
const cache = await caches.open(STATIC_CACHE_NAME);
|
350 |
+
const cachedResponse = await cache.match(request);
|
351 |
+
|
352 |
+
if (cachedResponse) {
|
353 |
+
console.log('Serving static resource from cache:', request.url);
|
354 |
+
return cachedResponse;
|
355 |
+
}
|
356 |
+
|
357 |
+
// No cached version available
|
358 |
+
throw networkError;
|
359 |
+
}
|
360 |
+
} catch (error) {
|
361 |
+
console.error('Error handling static request:', error);
|
362 |
+
return new Response('Resource not available offline', {
|
363 |
+
status: 503,
|
364 |
+
statusText: 'Service Unavailable'
|
365 |
+
});
|
366 |
+
}
|
367 |
+
}
|
368 |
+
|
369 |
+
// Handle audio file requests
|
370 |
+
async function handleAudioRequest(request) {
|
371 |
+
try {
|
372 |
+
const cache = await caches.open(CACHE_NAME);
|
373 |
+
const cachedResponse = await cache.match(request);
|
374 |
+
|
375 |
+
if (cachedResponse) {
|
376 |
+
console.log('Serving audio from cache:', request.url);
|
377 |
+
return cachedResponse;
|
378 |
+
}
|
379 |
+
|
380 |
+
// Not in cache, fetch from network
|
381 |
+
console.log('Fetching audio from network:', request.url);
|
382 |
+
const networkResponse = await fetch(request);
|
383 |
+
|
384 |
+
if (networkResponse.ok) {
|
385 |
+
// Cache the response for future use
|
386 |
+
const responseClone = networkResponse.clone();
|
387 |
+
await cache.put(request, responseClone);
|
388 |
+
console.log('Audio cached successfully:', request.url);
|
389 |
+
}
|
390 |
+
|
391 |
+
return networkResponse;
|
392 |
+
} catch (error) {
|
393 |
+
console.error('Error handling audio request:', error);
|
394 |
+
// Return a fallback or error response
|
395 |
+
return new Response('Audio not available offline', {
|
396 |
+
status: 503,
|
397 |
+
statusText: 'Service Unavailable'
|
398 |
+
});
|
399 |
+
}
|
400 |
+
}
|
401 |
+
|
402 |
+
// Handle audio download API requests
|
403 |
+
async function handleAudioDownload(request) {
|
404 |
+
try {
|
405 |
+
const cache = await caches.open(CACHE_NAME);
|
406 |
+
const cachedResponse = await cache.match(request);
|
407 |
+
|
408 |
+
if (cachedResponse) {
|
409 |
+
console.log('Serving download from cache:', request.url);
|
410 |
+
return cachedResponse;
|
411 |
+
}
|
412 |
+
|
413 |
+
// Fetch from network and cache
|
414 |
+
const networkResponse = await fetch(request);
|
415 |
+
|
416 |
+
if (networkResponse.ok) {
|
417 |
+
const responseClone = networkResponse.clone();
|
418 |
+
await cache.put(request, responseClone);
|
419 |
+
console.log('Download cached successfully:', request.url);
|
420 |
+
}
|
421 |
+
|
422 |
+
return networkResponse;
|
423 |
+
} catch (error) {
|
424 |
+
console.error('Error handling download request:', error);
|
425 |
+
return new Response('Download not available offline', {
|
426 |
+
status: 503,
|
427 |
+
statusText: 'Service Unavailable'
|
428 |
+
});
|
429 |
+
}
|
430 |
+
}
|
431 |
+
|
432 |
+
// Handle stream info requests
|
433 |
+
async function handleStreamRequest(request) {
|
434 |
+
try {
|
435 |
+
const cache = await caches.open(STATIC_CACHE_NAME);
|
436 |
+
const cachedResponse = await cache.match(request);
|
437 |
+
|
438 |
+
// For stream info, we can cache for a short time
|
439 |
+
if (cachedResponse) {
|
440 |
+
const cacheDate = new Date(cachedResponse.headers.get('sw-cache-date') || 0);
|
441 |
+
const now = new Date();
|
442 |
+
const maxAge = 30 * 60 * 1000; // 30 minutes
|
443 |
+
|
444 |
+
if (now - cacheDate < maxAge) {
|
445 |
+
console.log('Serving stream info from cache:', request.url);
|
446 |
+
return cachedResponse;
|
447 |
+
}
|
448 |
+
}
|
449 |
+
|
450 |
+
// Fetch fresh data
|
451 |
+
const networkResponse = await fetch(request);
|
452 |
+
|
453 |
+
if (networkResponse.ok) {
|
454 |
+
const responseClone = networkResponse.clone();
|
455 |
+
const headers = new Headers(responseClone.headers);
|
456 |
+
headers.set('sw-cache-date', new Date().toISOString());
|
457 |
+
|
458 |
+
const cachedResponse = new Response(responseClone.body, {
|
459 |
+
status: responseClone.status,
|
460 |
+
statusText: responseClone.statusText,
|
461 |
+
headers: headers
|
462 |
+
});
|
463 |
+
|
464 |
+
await cache.put(request, cachedResponse);
|
465 |
+
}
|
466 |
+
|
467 |
+
return networkResponse;
|
468 |
+
} catch (error) {
|
469 |
+
console.error('Error handling stream request:', error);
|
470 |
+
return fetch(request);
|
471 |
+
}
|
472 |
+
}
|
473 |
+
|
474 |
+
// Message handling for manual cache operations
|
475 |
+
self.addEventListener('message', event => {
|
476 |
+
if (event.data && event.data.type === 'CACHE_AUDIO') {
|
477 |
+
event.waitUntil(cacheAudioFile(event.data.url));
|
478 |
+
}
|
479 |
+
|
480 |
+
if (event.data && event.data.type === 'CLEAR_CACHE') {
|
481 |
+
event.waitUntil(clearCache());
|
482 |
+
}
|
483 |
+
|
484 |
+
if (event.data && event.data.type === 'GET_CACHE_SIZE') {
|
485 |
+
event.waitUntil(getCacheSize().then(size => {
|
486 |
+
event.ports[0].postMessage({ size });
|
487 |
+
}));
|
488 |
+
}
|
489 |
+
});
|
490 |
+
|
491 |
+
// Manually cache an audio file
|
492 |
+
async function cacheAudioFile(url) {
|
493 |
+
try {
|
494 |
+
const cache = await caches.open(CACHE_NAME);
|
495 |
+
const response = await fetch(url);
|
496 |
+
|
497 |
+
if (response.ok) {
|
498 |
+
await cache.put(url, response);
|
499 |
+
console.log('Audio file cached manually:', url);
|
500 |
+
return true;
|
501 |
+
}
|
502 |
+
return false;
|
503 |
+
} catch (error) {
|
504 |
+
console.error('Error caching audio file:', error);
|
505 |
+
return false;
|
506 |
+
}
|
507 |
+
}
|
508 |
+
|
509 |
+
// Clear all caches
|
510 |
+
async function clearCache() {
|
511 |
+
try {
|
512 |
+
const cacheNames = await caches.keys();
|
513 |
+
await Promise.all(
|
514 |
+
cacheNames.map(cacheName => caches.delete(cacheName))
|
515 |
+
);
|
516 |
+
console.log('All caches cleared');
|
517 |
+
} catch (error) {
|
518 |
+
console.error('Error clearing cache:', error);
|
519 |
+
}
|
520 |
+
}
|
521 |
+
|
522 |
+
// Get cache size
|
523 |
+
async function getCacheSize() {
|
524 |
+
try {
|
525 |
+
const cache = await caches.open(CACHE_NAME);
|
526 |
+
const requests = await cache.keys();
|
527 |
+
let totalSize = 0;
|
528 |
+
|
529 |
+
for (const request of requests) {
|
530 |
+
const response = await cache.match(request);
|
531 |
+
if (response) {
|
532 |
+
const blob = await response.blob();
|
533 |
+
totalSize += blob.size;
|
534 |
+
}
|
535 |
+
}
|
536 |
+
|
537 |
+
return totalSize;
|
538 |
+
} catch (error) {
|
539 |
+
console.error('Error calculating cache size:', error);
|
540 |
+
return 0;
|
541 |
+
}
|
542 |
+
}
|
dist/public/waveform-icon.svg
ADDED
|