diff --git a/frontend/src/lib/components/ui/command/command-dialog.svelte b/frontend/src/lib/components/ui/command/command-dialog.svelte
new file mode 100644
index 00000000..ecbf940f
--- /dev/null
+++ b/frontend/src/lib/components/ui/command/command-dialog.svelte
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/lib/components/ui/command/command-empty.svelte b/frontend/src/lib/components/ui/command/command-empty.svelte
new file mode 100644
index 00000000..f5e83e23
--- /dev/null
+++ b/frontend/src/lib/components/ui/command/command-empty.svelte
@@ -0,0 +1,15 @@
+
+
+
+
+
diff --git a/frontend/src/lib/components/ui/command/command-group.svelte b/frontend/src/lib/components/ui/command/command-group.svelte
new file mode 100644
index 00000000..198a3df3
--- /dev/null
+++ b/frontend/src/lib/components/ui/command/command-group.svelte
@@ -0,0 +1,18 @@
+
+
+
+
+
diff --git a/frontend/src/lib/components/ui/command/command-input.svelte b/frontend/src/lib/components/ui/command/command-input.svelte
new file mode 100644
index 00000000..057193a3
--- /dev/null
+++ b/frontend/src/lib/components/ui/command/command-input.svelte
@@ -0,0 +1,23 @@
+
+
+
+
+
+
diff --git a/frontend/src/lib/components/ui/command/command-item.svelte b/frontend/src/lib/components/ui/command/command-item.svelte
new file mode 100644
index 00000000..89a9e970
--- /dev/null
+++ b/frontend/src/lib/components/ui/command/command-item.svelte
@@ -0,0 +1,19 @@
+
+
+
+
+
diff --git a/frontend/src/lib/components/ui/command/command-list.svelte b/frontend/src/lib/components/ui/command/command-list.svelte
new file mode 100644
index 00000000..6b1c915c
--- /dev/null
+++ b/frontend/src/lib/components/ui/command/command-list.svelte
@@ -0,0 +1,15 @@
+
+
+
+
+
diff --git a/frontend/src/lib/components/ui/command/command-separator.svelte b/frontend/src/lib/components/ui/command/command-separator.svelte
new file mode 100644
index 00000000..249008b6
--- /dev/null
+++ b/frontend/src/lib/components/ui/command/command-separator.svelte
@@ -0,0 +1,13 @@
+
+
+
diff --git a/frontend/src/lib/components/ui/command/command-shortcut.svelte b/frontend/src/lib/components/ui/command/command-shortcut.svelte
new file mode 100644
index 00000000..ede60875
--- /dev/null
+++ b/frontend/src/lib/components/ui/command/command-shortcut.svelte
@@ -0,0 +1,19 @@
+
+
+
+
+
diff --git a/frontend/src/lib/components/ui/command/command.svelte b/frontend/src/lib/components/ui/command/command.svelte
new file mode 100644
index 00000000..28067593
--- /dev/null
+++ b/frontend/src/lib/components/ui/command/command.svelte
@@ -0,0 +1,22 @@
+
+
+
+
+
diff --git a/frontend/src/lib/components/ui/command/index.ts b/frontend/src/lib/components/ui/command/index.ts
new file mode 100644
index 00000000..6fdefa6b
--- /dev/null
+++ b/frontend/src/lib/components/ui/command/index.ts
@@ -0,0 +1,37 @@
+import { Command as CommandPrimitive } from "cmdk-sv";
+
+import Root from "./command.svelte";
+import Dialog from "./command-dialog.svelte";
+import Empty from "./command-empty.svelte";
+import Group from "./command-group.svelte";
+import Item from "./command-item.svelte";
+import Input from "./command-input.svelte";
+import List from "./command-list.svelte";
+import Separator from "./command-separator.svelte";
+import Shortcut from "./command-shortcut.svelte";
+
+const Loading = CommandPrimitive.Loading;
+
+export {
+ Root,
+ Dialog,
+ Empty,
+ Group,
+ Item,
+ Input,
+ List,
+ Separator,
+ Shortcut,
+ Loading,
+ //
+ Root as Command,
+ Dialog as CommandDialog,
+ Empty as CommandEmpty,
+ Group as CommandGroup,
+ Item as CommandItem,
+ Input as CommandInput,
+ List as CommandList,
+ Separator as CommandSeparator,
+ Shortcut as CommandShortcut,
+ Loading as CommandLoading
+};
diff --git a/frontend/src/lib/components/ui/dialog/dialog-content.svelte b/frontend/src/lib/components/ui/dialog/dialog-content.svelte
new file mode 100644
index 00000000..8646900a
--- /dev/null
+++ b/frontend/src/lib/components/ui/dialog/dialog-content.svelte
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+ Close
+
+
+
diff --git a/frontend/src/lib/components/ui/dialog/dialog-description.svelte b/frontend/src/lib/components/ui/dialog/dialog-description.svelte
new file mode 100644
index 00000000..7250e093
--- /dev/null
+++ b/frontend/src/lib/components/ui/dialog/dialog-description.svelte
@@ -0,0 +1,16 @@
+
+
+
+
+
diff --git a/frontend/src/lib/components/ui/dialog/dialog-footer.svelte b/frontend/src/lib/components/ui/dialog/dialog-footer.svelte
new file mode 100644
index 00000000..36c0cca6
--- /dev/null
+++ b/frontend/src/lib/components/ui/dialog/dialog-footer.svelte
@@ -0,0 +1,19 @@
+
+
+
+
+
diff --git a/frontend/src/lib/components/ui/dialog/dialog-header.svelte b/frontend/src/lib/components/ui/dialog/dialog-header.svelte
new file mode 100644
index 00000000..82fbe51f
--- /dev/null
+++ b/frontend/src/lib/components/ui/dialog/dialog-header.svelte
@@ -0,0 +1,16 @@
+
+
+
+
+
diff --git a/frontend/src/lib/components/ui/dialog/dialog-overlay.svelte b/frontend/src/lib/components/ui/dialog/dialog-overlay.svelte
new file mode 100644
index 00000000..9524e520
--- /dev/null
+++ b/frontend/src/lib/components/ui/dialog/dialog-overlay.svelte
@@ -0,0 +1,24 @@
+
+
+
diff --git a/frontend/src/lib/components/ui/dialog/dialog-portal.svelte b/frontend/src/lib/components/ui/dialog/dialog-portal.svelte
new file mode 100644
index 00000000..eb5d0a57
--- /dev/null
+++ b/frontend/src/lib/components/ui/dialog/dialog-portal.svelte
@@ -0,0 +1,8 @@
+
+
+
+
+
diff --git a/frontend/src/lib/components/ui/dialog/dialog-title.svelte b/frontend/src/lib/components/ui/dialog/dialog-title.svelte
new file mode 100644
index 00000000..fb863c9c
--- /dev/null
+++ b/frontend/src/lib/components/ui/dialog/dialog-title.svelte
@@ -0,0 +1,16 @@
+
+
+
+
+
diff --git a/frontend/src/lib/components/ui/dialog/index.ts b/frontend/src/lib/components/ui/dialog/index.ts
new file mode 100644
index 00000000..c05c48e8
--- /dev/null
+++ b/frontend/src/lib/components/ui/dialog/index.ts
@@ -0,0 +1,34 @@
+import { Dialog as DialogPrimitive } from "bits-ui";
+
+const Root = DialogPrimitive.Root;
+const Trigger = DialogPrimitive.Trigger;
+
+import Title from "./dialog-title.svelte";
+import Portal from "./dialog-portal.svelte";
+import Footer from "./dialog-footer.svelte";
+import Header from "./dialog-header.svelte";
+import Overlay from "./dialog-overlay.svelte";
+import Content from "./dialog-content.svelte";
+import Description from "./dialog-description.svelte";
+
+export {
+ Root,
+ Title,
+ Portal,
+ Footer,
+ Header,
+ Trigger,
+ Overlay,
+ Content,
+ Description,
+ //
+ Root as Dialog,
+ Title as DialogTitle,
+ Portal as DialogPortal,
+ Footer as DialogFooter,
+ Header as DialogHeader,
+ Trigger as DialogTrigger,
+ Overlay as DialogOverlay,
+ Content as DialogContent,
+ Description as DialogDescription
+};
diff --git a/frontend/src/lib/schemas/setting.ts b/frontend/src/lib/schemas/setting.ts
index 4f79174c..9020d6b3 100644
--- a/frontend/src/lib/schemas/setting.ts
+++ b/frontend/src/lib/schemas/setting.ts
@@ -10,6 +10,18 @@ export const generalSettingsSchema = z.object({
export const plexSettingsSchema = z.object({
user: z.string().min(1),
token: z.string().min(1),
- url: z.string().min(1),
+ url: z.string().url().min(1),
watchlist: z.string().optional().default('')
});
+
+export const contentSettingsSchema = z.object({
+ overseerr_url: z.string().url().optional().default(''),
+ overseerr_api_key: z.string().optional().default(''),
+ mdblist_api_key: z.string().optional().default(''),
+ mdblist_update_interval: z.number().int().min(1).default(80),
+ mdblist_lists: z.string().array().default([''])
+});
+
+type GeneralSettings = z.infer
;
+type PlexSettings = z.infer;
+type ContentSettings = z.infer;
diff --git a/frontend/src/nprogress.css b/frontend/src/nprogress.css
new file mode 100644
index 00000000..fa84bdb2
--- /dev/null
+++ b/frontend/src/nprogress.css
@@ -0,0 +1,83 @@
+/* Make clicks pass-through */
+#nprogress {
+ pointer-events: none;
+}
+
+#nprogress .bar {
+ background: red;
+
+ position: fixed;
+ z-index: 1031;
+ top: 0;
+ left: 0;
+
+ width: 100%;
+ height: 3px;
+}
+
+/* Fancy blur effect */
+#nprogress .peg {
+ display: block;
+ position: absolute;
+ right: 0px;
+ width: 100px;
+ height: 100%;
+ box-shadow:
+ 0 0 10px red,
+ 0 0 5px red;
+ opacity: 1;
+
+ -webkit-transform: rotate(3deg) translate(0px, -4px);
+ -ms-transform: rotate(3deg) translate(0px, -4px);
+ transform: rotate(3deg) translate(0px, -4px);
+}
+
+/* Remove these to get rid of the spinner */
+#nprogress .spinner {
+ display: block;
+ position: fixed;
+ z-index: 1031;
+ top: 15px;
+ right: 15px;
+}
+
+#nprogress .spinner-icon {
+ width: 18px;
+ height: 18px;
+ box-sizing: border-box;
+
+ border: solid 2px transparent;
+ border-top-color: red;
+ border-left-color: red;
+ border-radius: 50%;
+
+ -webkit-animation: nprogress-spinner 400ms linear infinite;
+ animation: nprogress-spinner 400ms linear infinite;
+}
+
+.nprogress-custom-parent {
+ overflow: hidden;
+ position: relative;
+}
+
+.nprogress-custom-parent #nprogress .spinner,
+.nprogress-custom-parent #nprogress .bar {
+ position: absolute;
+}
+
+@-webkit-keyframes nprogress-spinner {
+ 0% {
+ -webkit-transform: rotate(0deg);
+ }
+ 100% {
+ -webkit-transform: rotate(360deg);
+ }
+}
+@keyframes nprogress-spinner {
+ 0% {
+ transform: rotate(0deg);
+ }
+ 100% {
+ transform: rotate(360deg);
+ }
+}
diff --git a/frontend/src/routes/+layout.svelte b/frontend/src/routes/+layout.svelte
index 70d40124..afd64432 100644
--- a/frontend/src/routes/+layout.svelte
+++ b/frontend/src/routes/+layout.svelte
@@ -1,8 +1,37 @@
-
@@ -12,3 +41,69 @@
diff --git a/frontend/src/routes/settings/+layout.svelte b/frontend/src/routes/settings/+layout.svelte
index 014a0cee..59f754a2 100644
--- a/frontend/src/routes/settings/+layout.svelte
+++ b/frontend/src/routes/settings/+layout.svelte
@@ -52,13 +52,13 @@