From eec832ac17af248c642a82b888f297bb287600d2 Mon Sep 17 00:00:00 2001 From: Beilin Date: Fri, 9 Jan 2026 20:12:38 +0300 Subject: [PATCH] nav+ departments+ home --- package-lock.json | 91 +- package.json | 5 +- postcss.config.js | 2 +- src/App.css | 64 +- src/App.jsx | 76 +- .../BackgroundCanvas/BackgroundCanvas.css | 121 +++ .../BackgroundCanvas/BackgroundCanvas.jsx | 237 +++++ src/Components/ImagePreloader.jsx | 46 +- src/Components/Nav/Navbar.jsx | 871 +++++++++++++----- src/Components/Sections/About/About.jsx | 732 +++++++++++++-- .../DepartmentDetail/DepartmentDetail.jsx | 46 +- .../DepartmentDetail2/DepartmentDetail2.jsx | 542 +++++++++++ .../Sections/Departments/Departments.jsx | 52 +- src/Components/Sections/Home/Home.jsx | 408 ++++++-- src/assets/Images/d12.jpeg | Bin 0 -> 139868 bytes src/assets/Images/d13.jpeg | Bin 0 -> 93079 bytes src/assets/Images/d14.jpg | Bin 0 -> 65180 bytes src/assets/Images/d15.jpg | Bin 0 -> 45545 bytes src/assets/Images/d16.jpg | Bin 0 -> 53713 bytes src/assets/Images/d17.png | Bin 0 -> 632987 bytes src/assets/Images/d18.jpg | Bin 0 -> 387799 bytes src/assets/Images/d19.jpeg | Bin 0 -> 69425 bytes src/assets/Images/d20.jpeg | Bin 0 -> 52222 bytes src/assets/Images/d21.jpeg | Bin 0 -> 72674 bytes src/assets/Images/d22.jpg | Bin 0 -> 125564 bytes src/assets/Images/d7.jpeg | Bin 0 -> 135014 bytes src/assets/Images/d7.png | Bin 311045 -> 0 bytes src/assets/LOGO.png | Bin 63229 -> 58508 bytes src/assets/TPS-logo.png | Bin 98089 -> 128077 bytes src/index.css | 79 +- src/main.jsx | 21 +- tailwind.config.js | 46 +- vite.config.js | 18 +- 33 files changed, 2814 insertions(+), 643 deletions(-) create mode 100644 src/Components/BackgroundCanvas/BackgroundCanvas.css create mode 100644 src/Components/BackgroundCanvas/BackgroundCanvas.jsx create mode 100644 src/Components/Sections/DepartmentDetail2/DepartmentDetail2.jsx create mode 100644 src/assets/Images/d12.jpeg create mode 100644 src/assets/Images/d13.jpeg create mode 100644 src/assets/Images/d14.jpg create mode 100644 src/assets/Images/d15.jpg create mode 100644 src/assets/Images/d16.jpg create mode 100644 src/assets/Images/d17.png create mode 100644 src/assets/Images/d18.jpg create mode 100644 src/assets/Images/d19.jpeg create mode 100644 src/assets/Images/d20.jpeg create mode 100644 src/assets/Images/d21.jpeg create mode 100644 src/assets/Images/d22.jpg create mode 100644 src/assets/Images/d7.jpeg delete mode 100644 src/assets/Images/d7.png diff --git a/package-lock.json b/package-lock.json index 57b4ae0..edc6609 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.0.0", "dependencies": { "@emailjs/browser": "^4.4.1", - "@tailwindcss/vite": "^4.1.18", + "@heroicons/react": "^2.2.0", "aos": "^2.3.4", "flowbite": "^4.0.1", "flowbite-react": "^0.12.16", @@ -27,7 +27,6 @@ }, "devDependencies": { "@eslint/js": "^9.39.1", - "@tailwindcss/postcss": "^4.1.18", "@types/react": "^19.2.5", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^5.1.1", @@ -37,7 +36,7 @@ "eslint-plugin-react-refresh": "^0.4.24", "globals": "^16.5.0", "postcss": "^8.5.6", - "tailwindcss": "^3.3.6", + "tailwindcss": "^3.4.0", "vite": "^7.2.4" } }, @@ -383,6 +382,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -399,6 +399,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -415,6 +416,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -431,6 +433,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -447,6 +450,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -463,6 +467,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -479,6 +484,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -495,6 +501,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -511,6 +518,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -527,6 +535,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -543,6 +552,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -559,6 +569,7 @@ "cpu": [ "loong64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -575,6 +586,7 @@ "cpu": [ "mips64el" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -591,6 +603,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -607,6 +620,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -623,6 +637,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -639,6 +654,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -655,6 +671,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -671,6 +688,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -687,6 +705,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -703,6 +722,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -719,6 +739,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -735,6 +756,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -751,6 +773,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -767,6 +790,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -783,6 +807,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1002,6 +1027,15 @@ "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", "license": "MIT" }, + "node_modules/@heroicons/react": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@heroicons/react/-/react-2.2.0.tgz", + "integrity": "sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ==", + "license": "MIT", + "peerDependencies": { + "react": ">= 16 || ^19.0.0-rc" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -1210,6 +1244,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1223,6 +1258,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1236,6 +1272,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1249,6 +1286,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1262,6 +1300,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1275,6 +1314,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1288,6 +1328,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1301,6 +1342,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1314,6 +1356,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1327,6 +1370,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1340,6 +1384,7 @@ "cpu": [ "loong64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1353,6 +1398,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1366,6 +1412,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1379,6 +1426,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1392,6 +1440,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1405,6 +1454,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1418,6 +1468,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1431,6 +1482,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1444,6 +1496,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1457,6 +1510,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1470,6 +1524,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1483,6 +1538,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1757,26 +1813,6 @@ "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==", "license": "MIT" }, - "node_modules/@tailwindcss/vite": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.18.tgz", - "integrity": "sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA==", - "license": "MIT", - "dependencies": { - "@tailwindcss/node": "4.1.18", - "@tailwindcss/oxide": "4.1.18", - "tailwindcss": "4.1.18" - }, - "peerDependencies": { - "vite": "^5.2.0 || ^6 || ^7" - } - }, - "node_modules/@tailwindcss/vite/node_modules/tailwindcss": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", - "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==", - "license": "MIT" - }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -2616,6 +2652,7 @@ "version": "0.27.2", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "dev": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -4563,6 +4600,7 @@ "version": "4.54.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.54.0.tgz", "integrity": "sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw==", + "devOptional": true, "license": "MIT", "peer": true, "dependencies": { @@ -4856,9 +4894,9 @@ } }, "node_modules/tailwindcss": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.6.tgz", - "integrity": "sha512-AKjF7qbbLvLaPieoKeTjG1+FyNZT6KaJMJPFeQyLfIp7l82ggH1fbHJSsYIvnbTFQOlkh+gBYpyby5GT1LIdLw==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.0.tgz", + "integrity": "sha512-VigzymniH77knD1dryXbyxR+ePHihHociZbXnLZHUyzf2MMs2ZVqlUrZ3FvpXP8pno9JzmILt1sZPD19M3IxtA==", "license": "MIT", "peer": true, "dependencies": { @@ -5081,6 +5119,7 @@ "version": "7.3.0", "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.0.tgz", "integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==", + "dev": true, "license": "MIT", "peer": true, "dependencies": { diff --git a/package.json b/package.json index 845ae7c..b8858fd 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ }, "dependencies": { "@emailjs/browser": "^4.4.1", - "@tailwindcss/vite": "^4.1.18", + "@heroicons/react": "^2.2.0", "aos": "^2.3.4", "flowbite": "^4.0.1", "flowbite-react": "^0.12.16", @@ -29,7 +29,6 @@ }, "devDependencies": { "@eslint/js": "^9.39.1", - "@tailwindcss/postcss": "^4.1.18", "@types/react": "^19.2.5", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^5.1.1", @@ -39,7 +38,7 @@ "eslint-plugin-react-refresh": "^0.4.24", "globals": "^16.5.0", "postcss": "^8.5.6", - "tailwindcss": "^3.3.6", + "tailwindcss": "^3.4.0", "vite": "^7.2.4" } } diff --git a/postcss.config.js b/postcss.config.js index 5aa5df0..ea52a08 100644 --- a/postcss.config.js +++ b/postcss.config.js @@ -2,5 +2,5 @@ export default { plugins: { tailwindcss: {}, autoprefixer: {}, - }, + } } \ No newline at end of file diff --git a/src/App.css b/src/App.css index d3c26f5..7217d1e 100644 --- a/src/App.css +++ b/src/App.css @@ -1,26 +1,56 @@ -.App { - min-height: 100vh; - background-color: white; - color: #1e293b; - transition: background-color 0.3s ease, color 0.3s ease; +.min-h-screen.bg-white.dark\:bg-gray-900 {} + +.bg-white, +.bg-gray-50, +.dark\:bg-gray-800, +.dark\:bg-gray-900 { + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); } -.dark .App { - background-color: #0f172a; - color: #f1f5f9; +.dark .bg-white, +.dark .bg-gray-50, +.dark .dark\:bg-gray-800, +.dark .dark\:bg-gray-900 {} + +.border, +.border-gray-200, +.dark\:border-gray-700 { + border-color: rgba(var(--border-color), 0.3) !important; } -:root { - --app-bg: white; - --app-text: #1e293b; +.text-gray-900, +.dark\:text-white { + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); } -.dark { - --app-bg: #0f172a; - --app-text: #f1f5f9; +.dark .text-gray-900, +.dark .dark\:text-white { + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3); } -.App { - background-color: var(--app-bg); - color: var(--app-text); +.bg-opacity-95 { + backdrop-filter: blur(10px); +} + +.shadow-lg, +.shadow-xl { + box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1) !important; +} + +.dark .shadow-lg, +.dark .shadow-xl { + box-shadow: 0 10px 25px rgba(0, 0, 0, 0.3) !important; +} + +.bg-gradient-to-r, +.bg-gradient-to-l, +.bg-gradient-to-t, +.bg-gradient-to-b { + background-blend-mode: overlay; +} + +.fixed.top-0 { + backdrop-filter: blur(15px); + -webkit-backdrop-filter: blur(15px); } \ No newline at end of file diff --git a/src/App.jsx b/src/App.jsx index a003073..83af1cf 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,4 +1,4 @@ -// App.jsx +// src/App.jsx import React from "react"; import { BrowserRouter, Routes, Route, useLocation } from "react-router-dom"; import { AnimatePresence, LayoutGroup } from "framer-motion"; @@ -11,42 +11,40 @@ import Home from "./Components/Sections/Home/Home"; import Services from "./Components/Sections/Services/Services"; import About from "./Components/Sections/About/About"; import Departments from "./Components/Sections/Departments/Departments"; -import DepartmentDetail from "./Components/Sections/DepartmentDetail/DepartmentDetail"; // صفحة التفاصيل (مقدّمة سابقاً) +import DepartmentDetail from "./Components/Sections/DepartmentDetail/DepartmentDetail"; import Contact from "./Components/Sections/Contact/Contact"; -// import ImagePreloader from "./Components/ImagePreloader"; import Footer from "./Components/Nav/Footer"; +import DepartmentDetail2 from "./Components/Sections/DepartmentDetail2/DepartmentDetail2"; - - +/* MainPage — الصفحة التي تحتوي على الأقسام المجمعة (لا تتضمن Navbar هنا) */ const MainPage = () => { return ( -
-
- -
- - - - - - -
-
-
+
+
+
+ + + + + +
+
+
); }; -// Router view (نستخدم useLocation مع AnimatePresence لعمل انتقالات سلسة بين المسارات) +/* Router view يستخدم useLocation مع AnimatePresence */ function RouterView() { const location = useLocation(); return ( - + } /> - } /> + } /> + } /> {/* أضف مسارات إضافية هنا إذا احتجت */} @@ -54,10 +52,44 @@ function RouterView() { ); } +/* Layout: يقرر متى يُعرض الـ Navbar بناءً على الـ pathname */ +function Layout() { + const location = useLocation(); + + // هنا ضع المسارات أو الأنماط التي تريد إخفاء الـ Navbar فيها + const excludedExactPaths = [ + "/department-detail2", + // أضف مسارات أخرى تريد إخفاء navbar فيها بنفس الشكل + ]; + + // أمثلة على أنماط: إخفاء لكل ما يبدأ بـ /departments/ + const excludedPrefixes = ["/departments/"]; + + const isExcludedExact = excludedExactPaths.includes(location.pathname); + const isExcludedPrefix = excludedPrefixes.some((p) => location.pathname.startsWith(p)); + + const hideNavbar = isExcludedExact || isExcludedPrefix; + + // ارتفاع الـ Navbar المستخدم كـ padding-top عندما يكون ظاهرًا (يتوافق مع height في Navbar.jsx) + const navbarHeight = hideNavbar ? 0 : 56; + + return ( + <> + {/* Navbar يظهر فقط عندما hideNavbar === false */} + {!hideNavbar && } + + {/* نضع الـ RouterView داخل حاوية لها padding-top مساوٍ لارتفاع الـ navbar عند ظهوره */} +
+ +
+ + ); +} + const App = () => { return ( - + ); }; diff --git a/src/Components/BackgroundCanvas/BackgroundCanvas.css b/src/Components/BackgroundCanvas/BackgroundCanvas.css new file mode 100644 index 0000000..b96339d --- /dev/null +++ b/src/Components/BackgroundCanvas/BackgroundCanvas.css @@ -0,0 +1,121 @@ +html { + overflow-x: hidden !important; + height: 100% !important; +} + +body { + margin: 0 !important; + padding: 0 !important; + width: 100% !important; + min-height: 100% !important; + position: relative !important; + overflow-x: hidden !important; + overflow-y: auto !important; +} + +.background-canvas { + position: fixed !important; + top: 0 !important; + left: 0 !important; + width: 100vw !important; + height: 100vh !important; + z-index: -1 !important; + pointer-events: none !important; + overflow: hidden !important; +} + +::-webkit-scrollbar { + width: 0px !important; + height: 0px !important; + background: transparent !important; +} + +::-webkit-scrollbar-track { + background: transparent !important; +} + +::-webkit-scrollbar-thumb { + background: transparent !important; + border-radius: 0px !important; +} + +::-webkit-scrollbar-thumb:hover { + background: transparent !important; +} + +* { + scrollbar-width: none !important; + scrollbar-color: transparent transparent !important; +} + +* { + -ms-overflow-style: -ms-autohiding-scrollbar !important; +} + +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +main { + position: relative !important; + z-index: 10 !important; + min-height: 100vh !important; + width: 100% !important; + padding: 20px !important; +} + +nav.fixed { + z-index: 50 !important; + background: transparent !important; +} + +@media (max-width: 768px) { + .background-canvas { + opacity: 0.8 !important; + } + body { + -webkit-overflow-scrolling: touch !important; + overscroll-behavior-y: contain !important; + } + * { + -webkit-tap-highlight-color: transparent !important; + } +} + +.content-container { + position: relative !important; + z-index: 10 !important; + height: 100% !important; + overflow-y: auto !important; + overflow-x: hidden !important; + -webkit-overflow-scrolling: touch !important; +} + +.scroll-container::-webkit-scrollbar { + opacity: 0; + transition: opacity 0.3s ease; +} + +.scroll-container:hover::-webkit-scrollbar { + opacity: 1; +} + +@supports (-webkit-touch-callout: none) { + body { + height: -webkit-fill-available !important; + } + .background-canvas { + height: -webkit-fill-available !important; + } +} + +.fade-on-scroll { + opacity: 1; + transition: opacity 0.3s ease; +} + +.fade-on-scroll.hidden { + opacity: 0; +} \ No newline at end of file diff --git a/src/Components/BackgroundCanvas/BackgroundCanvas.jsx b/src/Components/BackgroundCanvas/BackgroundCanvas.jsx new file mode 100644 index 0000000..3cda436 --- /dev/null +++ b/src/Components/BackgroundCanvas/BackgroundCanvas.jsx @@ -0,0 +1,237 @@ +import React, { useRef, useEffect, useState } from 'react'; +import './BackgroundCanvas.css'; + +const BackgroundCanvas = ({ theme = 'light' }) => { + const canvasRef = useRef(null); + const animationRef = useRef(null); + const jointsRef = useRef([]); + const cameraRef = useRef(null); + const keysRef = useRef(new Array(127).fill(0)); + const worldRef = useRef({ width: 0, height: 0 }); + const themeRef = useRef(theme); + + class Camera { + constructor(position, zoom) { + this.position = position; + this.speed = 3; + this.acceleration = { x: 0, y: 0, z: 0 }; + this.zoom = zoom; + } + } + + class Joint { + constructor(position, vector) { + this.position = position; + this.vector = vector; + this.speed = 0.5; + this.w = 2; + this.h = 2; + this.bone_length = 150; + } + } + + const generateJoints = (numb, worldWidth, worldHeight) => { + const arr = new Array(numb).fill(0); + arr.forEach((v, i) => { + arr[i] = new Joint( + { + x: Math.random() * worldWidth, + y: Math.random() * worldHeight, + }, + { + x: Math.random() * 2 - 1, + y: Math.random() * 2 - 1, + } + ); + }); + return arr; + }; + + const moveJoints = (joints, world) => { + joints.forEach((joint) => { + joint.position.x += joint.vector.x * joint.speed; + joint.position.y += joint.vector.y * joint.speed; + + if (joint.position.x < 0) joint.position.x = world.width; + if (joint.position.x > world.width) joint.position.x = 0; + if (joint.position.y < 0) joint.position.y = world.height; + if (joint.position.y > world.height) joint.position.y = 0; + }); + }; + + const drawJoints = (ctx, joints, camera, view, theme) => { + const len = joints.length; + + let lineColor, pointColor, backgroundColor; + if (theme === 'dark') { + backgroundColor = '#000000'; + lineColor = '#FFFFFF'; + pointColor = '#F5EEE6'; + } else { + backgroundColor = '#FFFFFF'; + lineColor = '#131313'; + pointColor = '#041c40'; + } + + ctx.fillStyle = backgroundColor; + ctx.fillRect(0, 0, view.width, view.height); + + for (let i = 0; i < len; i++) { + for (let j = i + 1; j < len; j += 3) { + const length = Math.hypot( + joints[j].position.x - joints[i].position.x, + joints[j].position.y - joints[i].position.y + ); + + if (length <= joints[i].bone_length) { + ctx.beginPath(); + ctx.strokeStyle = lineColor; + ctx.lineWidth = camera.zoom * (30 / length); + ctx.moveTo( + view.width / 2 + (joints[i].position.x - camera.position.x) * camera.zoom, + view.height / 2 + (joints[i].position.y - camera.position.y) * camera.zoom + ); + ctx.lineTo( + view.width / 2 + (joints[j].position.x - camera.position.x) * camera.zoom, + view.height / 2 + (joints[j].position.y - camera.position.y) * camera.zoom + ); + ctx.stroke(); + ctx.closePath(); + } + } + + ctx.fillStyle = pointColor; + ctx.fillRect( + view.width / 2 + ((joints[i].position.x - camera.position.x) - joints[i].w / 2) * camera.zoom, + view.height / 2 + ((joints[i].position.y - camera.position.y) - joints[i].w / 2) * camera.zoom, + joints[i].w * camera.zoom, + joints[i].h * camera.zoom + ); + } + }; + + const moveCamera = (camera, keys) => { + if (keys[37]) camera.acceleration.x -= camera.speed; + if (keys[38]) camera.acceleration.y -= camera.speed; + if (keys[39]) camera.acceleration.x += camera.speed; + if (keys[40]) camera.acceleration.y += camera.speed; + if (keys[188]) camera.acceleration.z += 0.003; + if (keys[190]) camera.acceleration.z -= 0.003; + + camera.position.x += camera.acceleration.x; + camera.position.y += camera.acceleration.y; + camera.zoom += camera.acceleration.z; + + camera.acceleration.x *= 0.96; + camera.acceleration.y *= 0.96; + camera.acceleration.z *= 0.9; + }; + + const initCanvas = () => { + const canvas = canvasRef.current; + if (!canvas) return; + + const ctx = canvas.getContext('2d'); + const view = { + width: window.innerWidth, + height: window.innerHeight, + }; + + canvas.width = view.width; + canvas.height = view.height; + + worldRef.current = { + width: view.width * 2, + height: view.height * 2, + }; + + cameraRef.current = new Camera( + { + x: worldRef.current.width / 2, + y: worldRef.current.height / 2, + }, + 1 + ); + + jointsRef.current = generateJoints(300, worldRef.current.width, worldRef.current.height); + }; + + const animate = () => { + const canvas = canvasRef.current; + if (!canvas) return; + + const ctx = canvas.getContext('2d'); + const view = { + width: canvas.width, + height: canvas.height, + }; + + moveCamera(cameraRef.current, keysRef.current); + moveJoints(jointsRef.current, worldRef.current); + + drawJoints(ctx, jointsRef.current, cameraRef.current, view, themeRef.current); + + animationRef.current = requestAnimationFrame(animate); + }; + + useEffect(() => { + themeRef.current = theme; + }, [theme]); + + useEffect(() => { + const handleKeyDown = (e) => { + keysRef.current[e.keyCode] = 1; + }; + + const handleKeyUp = (e) => { + keysRef.current[e.keyCode] = 0; + }; + + window.addEventListener('keydown', handleKeyDown); + window.addEventListener('keyup', handleKeyUp); + + return () => { + window.removeEventListener('keydown', handleKeyDown); + window.removeEventListener('keyup', handleKeyUp); + }; + }, []); + + useEffect(() => { + const handleResize = () => { + const canvas = canvasRef.current; + if (!canvas) return; + + canvas.width = window.innerWidth; + canvas.height = window.innerHeight; + + worldRef.current = { + width: canvas.width * 2, + height: canvas.height * 2, + }; + }; + + window.addEventListener('resize', handleResize); + return () => window.removeEventListener('resize', handleResize); + }, []); + + useEffect(() => { + initCanvas(); + animate(); + + return () => { + if (animationRef.current) { + cancelAnimationFrame(animationRef.current); + } + }; + }, []); + + return ( +