diff --git a/frontend/.prettierrc.json b/frontend/.prettierrc.json
new file mode 100644
index 0000000..13040da
--- /dev/null
+++ b/frontend/.prettierrc.json
@@ -0,0 +1,9 @@
+{
+ "trailingComma": "es5",
+ "tabWidth": 4,
+ "semi": true,
+ "singleQuote": false,
+ "useTabs": false,
+ "printWidth": 100,
+ "plugins": ["prettier-plugin-tailwindcss"]
+}
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index e1d1c23..96a6f1f 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -11,7 +11,9 @@
"@tailwindcss/vite": "^4.1.11",
"react": "^19.1.0",
"react-dom": "^19.1.0",
+ "react-icons": "^5.5.0",
"react-router-dom": "^7.7.0",
+ "sonner": "^2.0.6",
"tailwindcss": "^4.1.11"
},
"devDependencies": {
@@ -20,10 +22,12 @@
"@types/react-dom": "^19.1.6",
"@vitejs/plugin-react": "^4.7.0",
"@vitejs/plugin-react-swc": "^3.11.0",
+ "daisyui": "^5.0.46",
"eslint": "^9.31.0",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.20",
"globals": "^16.3.0",
+ "prettier-plugin-tailwindcss": "^0.6.14",
"vite": "^7.0.5"
}
},
@@ -2093,6 +2097,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/daisyui": {
+ "version": "5.0.46",
+ "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-5.0.46.tgz",
+ "integrity": "sha512-vMDZK1tI/bOb2Mc3Mk5WpquBG3ZqBz1YKZ0xDlvpOvey60dOS4/5Qhdowq1HndbQl7PgDLDYysxAjjUjwR7/eQ==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/saadeghi/daisyui?sponsor=1"
+ }
+ },
"node_modules/debug": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
@@ -3204,6 +3218,110 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/prettier": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
+ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "bin": {
+ "prettier": "bin/prettier.cjs"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
+ "node_modules/prettier-plugin-tailwindcss": {
+ "version": "0.6.14",
+ "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.14.tgz",
+ "integrity": "sha512-pi2e/+ZygeIqntN+vC573BcW5Cve8zUB0SSAGxqpB4f96boZF4M3phPVoOFCeypwkpRYdi7+jQ5YJJUwrkGUAg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.21.3"
+ },
+ "peerDependencies": {
+ "@ianvs/prettier-plugin-sort-imports": "*",
+ "@prettier/plugin-hermes": "*",
+ "@prettier/plugin-oxc": "*",
+ "@prettier/plugin-pug": "*",
+ "@shopify/prettier-plugin-liquid": "*",
+ "@trivago/prettier-plugin-sort-imports": "*",
+ "@zackad/prettier-plugin-twig": "*",
+ "prettier": "^3.0",
+ "prettier-plugin-astro": "*",
+ "prettier-plugin-css-order": "*",
+ "prettier-plugin-import-sort": "*",
+ "prettier-plugin-jsdoc": "*",
+ "prettier-plugin-marko": "*",
+ "prettier-plugin-multiline-arrays": "*",
+ "prettier-plugin-organize-attributes": "*",
+ "prettier-plugin-organize-imports": "*",
+ "prettier-plugin-sort-imports": "*",
+ "prettier-plugin-style-order": "*",
+ "prettier-plugin-svelte": "*"
+ },
+ "peerDependenciesMeta": {
+ "@ianvs/prettier-plugin-sort-imports": {
+ "optional": true
+ },
+ "@prettier/plugin-hermes": {
+ "optional": true
+ },
+ "@prettier/plugin-oxc": {
+ "optional": true
+ },
+ "@prettier/plugin-pug": {
+ "optional": true
+ },
+ "@shopify/prettier-plugin-liquid": {
+ "optional": true
+ },
+ "@trivago/prettier-plugin-sort-imports": {
+ "optional": true
+ },
+ "@zackad/prettier-plugin-twig": {
+ "optional": true
+ },
+ "prettier-plugin-astro": {
+ "optional": true
+ },
+ "prettier-plugin-css-order": {
+ "optional": true
+ },
+ "prettier-plugin-import-sort": {
+ "optional": true
+ },
+ "prettier-plugin-jsdoc": {
+ "optional": true
+ },
+ "prettier-plugin-marko": {
+ "optional": true
+ },
+ "prettier-plugin-multiline-arrays": {
+ "optional": true
+ },
+ "prettier-plugin-organize-attributes": {
+ "optional": true
+ },
+ "prettier-plugin-organize-imports": {
+ "optional": true
+ },
+ "prettier-plugin-sort-imports": {
+ "optional": true
+ },
+ "prettier-plugin-style-order": {
+ "optional": true
+ },
+ "prettier-plugin-svelte": {
+ "optional": true
+ }
+ }
+ },
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@@ -3235,6 +3353,15 @@
"react": "^19.1.0"
}
},
+ "node_modules/react-icons": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz",
+ "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "*"
+ }
+ },
"node_modules/react-refresh": {
"version": "0.17.0",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
@@ -3377,6 +3504,16 @@
"node": ">=8"
}
},
+ "node_modules/sonner": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.6.tgz",
+ "integrity": "sha512-yHFhk8T/DK3YxjFQXIrcHT1rGEeTLliVzWbO0xN8GberVun2RiBnxAjXAYpZrqwEVHBG9asI/Li8TAAhN9m59Q==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc",
+ "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ }
+ },
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index 3e0425e..9826443 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -13,7 +13,9 @@
"@tailwindcss/vite": "^4.1.11",
"react": "^19.1.0",
"react-dom": "^19.1.0",
+ "react-icons": "^5.5.0",
"react-router-dom": "^7.7.0",
+ "sonner": "^2.0.6",
"tailwindcss": "^4.1.11"
},
"devDependencies": {
@@ -22,10 +24,12 @@
"@types/react-dom": "^19.1.6",
"@vitejs/plugin-react": "^4.7.0",
"@vitejs/plugin-react-swc": "^3.11.0",
+ "daisyui": "^5.0.46",
"eslint": "^9.31.0",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.20",
"globals": "^16.3.0",
+ "prettier-plugin-tailwindcss": "^0.6.14",
"vite": "^7.0.5"
}
}
diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
index 4b5b5ae..fb648e2 100644
--- a/frontend/src/App.jsx
+++ b/frontend/src/App.jsx
@@ -1,28 +1,41 @@
-import { useState, useEffect } from "react";
-import { HashRouter as Router, Routes, Route, Navigate } from "react-router-dom";
-import TopNav from "./components/topnav";
-import SideNav from "./components/sidenav";
+import { useEffect, useState } from "react";
+import { Navigate, Route, HashRouter as Router, Routes } from "react-router-dom";
+import About from "./components/about";
+import Container from "./components/container";
import Results from "./components/results";
import Settings from "./components/settings";
+import TabNavigation from "./components/tabnavigation";
-function App() {
- const [isSideNavOpen, setIsSideNavOpen] = useState(false);
- const [validConfig, setValidConfig] = useState(null); // null = loading
+const App = () => {
+ const [validConfig, setValidConfig] = useState(null);
useEffect(() => {
- chrome.storage.local.get("valid_config", (result) => {
- if (chrome.runtime.lastError) {
- console.error("Error reading valid_config:", chrome.runtime.lastError);
- setValidConfig(false); // fallback
- } else {
- setValidConfig(result.valid_config === true);
- }
- });
+ // Fix: Access chrome API properly for browser extensions
+ if (typeof chrome !== "undefined" && chrome.storage) {
+ chrome.storage.local.get("valid_config", (result) => {
+ if (chrome.runtime.lastError) {
+ console.error("Error reading valid_config:", chrome.runtime.lastError);
+ setValidConfig(false);
+ } else {
+ setValidConfig(result.valid_config === true);
+ }
+ });
+ } else {
+ // Fallback for development/testing
+ setValidConfig(false);
+ }
}, []);
+ const handleConfigSaved = () => {
+ setValidConfig(true);
+ // Navigate to main tab after config is saved
+ window.location.hash = "#/results";
+ };
+
if (validConfig === null) {
return (
-
+
+
Loading...
);
@@ -30,20 +43,16 @@ function App() {
return (
-
-
- setIsSideNavOpen(true)} />
-
-
-
+
+
+
+
{!validConfig ? (
<>
setValidConfig(true)} />
- }
+ element={}
/>
} />
>
@@ -52,21 +61,14 @@ function App() {
} />
} />
} />
+ } />
>
)}
-
-
-
- setIsSideNavOpen(false)} />
-
+
);
-}
+};
export default App;
diff --git a/frontend/src/assets/fonts/InterVariable-Italic.woff2 b/frontend/src/assets/fonts/InterVariable-Italic.woff2
new file mode 100644
index 0000000..b3530f3
Binary files /dev/null and b/frontend/src/assets/fonts/InterVariable-Italic.woff2 differ
diff --git a/frontend/src/assets/fonts/InterVariable.woff2 b/frontend/src/assets/fonts/InterVariable.woff2
new file mode 100644
index 0000000..5a8d3e7
Binary files /dev/null and b/frontend/src/assets/fonts/InterVariable.woff2 differ
diff --git a/frontend/src/assets/fonts/font-face.css b/frontend/src/assets/fonts/font-face.css
new file mode 100644
index 0000000..d12308e
--- /dev/null
+++ b/frontend/src/assets/fonts/font-face.css
@@ -0,0 +1,15 @@
+@font-face {
+ font-family: Inter;
+ src: url("./InterVariable.woff2");
+ font-style: normal;
+ font-weight: 300 900;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: Inter;
+ src: url("./InterVariable-Italic.woff2");
+ font-style: italic;
+ font-weight: 300 900;
+ font-display: swap;
+}
diff --git a/frontend/src/components/about.jsx b/frontend/src/components/about.jsx
new file mode 100644
index 0000000..331ac22
--- /dev/null
+++ b/frontend/src/components/about.jsx
@@ -0,0 +1,57 @@
+import { FaDiscord, FaTelegram } from "react-icons/fa";
+import { SiGitea } from "react-icons/si";
+
+const AboutPage = () => {
+ const socialLinks = [
+ {
+ name: "Discord",
+ icon:
,
+ url: "https://discord.cdrm-project.com/",
+ description: "Join our Discord community",
+ color: "hover:text-indigo-400",
+ },
+ {
+ name: "Telegram",
+ icon:
,
+ url: "https://telegram.cdrm-project.com/",
+ description: "Follow us on Telegram",
+ color: "hover:text-sky-400",
+ },
+ {
+ name: "Gitea",
+ icon:
,
+ url: "https://cdm-project.com/tpd94/cdrm-project",
+ description: "Check out our code",
+ color: "hover:text-lime-400",
+ },
+ ];
+
+ return (
+
+
+
Connect with us
+
Join our community and stay updated
+
+
+
+
+ );
+};
+
+export default AboutPage;
diff --git a/frontend/src/components/container.jsx b/frontend/src/components/container.jsx
new file mode 100644
index 0000000..d25cc18
--- /dev/null
+++ b/frontend/src/components/container.jsx
@@ -0,0 +1,9 @@
+const Container = ({ children, className = "", ...props }) => {
+ return (
+
+ {children}
+
+ );
+};
+
+export default Container;
diff --git a/frontend/src/components/injectionmenu.jsx b/frontend/src/components/injectionmenu.jsx
new file mode 100644
index 0000000..780ce93
--- /dev/null
+++ b/frontend/src/components/injectionmenu.jsx
@@ -0,0 +1,72 @@
+import { useEffect, useState } from "react";
+
+const InjectionMenu = () => {
+ const [injectionType, setInjectionType] = useState("LICENSE");
+ const [drmOverride, setDrmOverride] = useState("DISABLED");
+
+ useEffect(() => {
+ chrome.storage.local.get(["injection_type", "drm_override"], (result) => {
+ if (result.injection_type !== undefined) {
+ setInjectionType(result.injection_type);
+ }
+ if (result.drm_override !== undefined) {
+ setDrmOverride(result.drm_override);
+ }
+ });
+ }, []);
+
+ const handleInjectionTypeChange = (type) => {
+ chrome.storage.local.set({ injection_type: type }, () => {
+ if (chrome.runtime.lastError) {
+ console.error("Error updating injection_type:", chrome.runtime.lastError);
+ } else {
+ setInjectionType(type);
+ console.log(`Injection type updated to ${type}`);
+ }
+ });
+ };
+
+ const handleDrmOverrideChange = (type) => {
+ chrome.storage.local.set({ drm_override: type }, () => {
+ if (chrome.runtime.lastError) {
+ console.error("Error updating drm_override:", chrome.runtime.lastError);
+ } else {
+ setDrmOverride(type);
+ console.log(`DRM Override updated to ${type}`);
+ }
+ });
+ };
+
+ return (
+
+ );
+};
+
+export default InjectionMenu;
diff --git a/frontend/src/components/results.jsx b/frontend/src/components/results.jsx
index 4b60d67..42080f6 100644
--- a/frontend/src/components/results.jsx
+++ b/frontend/src/components/results.jsx
@@ -1,6 +1,9 @@
import React, { useEffect, useState } from "react";
+import { IoCameraOutline, IoCopyOutline, IoSaveOutline } from "react-icons/io5";
+import { toast } from "sonner";
+import InjectionMenu from "./injectionmenu";
-function Results() {
+const Results = () => {
const [drmType, setDrmType] = useState("");
const [pssh, setPssh] = useState("");
const [licenseUrl, setLicenseUrl] = useState("");
@@ -118,6 +121,11 @@ function Results() {
});
};
+ const handleCopyToClipboard = (text, label) => {
+ navigator.clipboard.writeText(text);
+ toast.success(`Copied ${label} to clipboard`);
+ };
+
// Check if current tab is YouTube
const isYouTube = () => {
return currentTabUrl.includes("youtube.com") || currentTabUrl.includes("youtu.be");
@@ -177,74 +185,137 @@ function Results() {
};
return (
-
-