From eb0218bdcb105ae1e5b96d8087df62ec9a4fd045 Mon Sep 17 00:00:00 2001 From: Muhammad Ibrahim Date: Tue, 28 Oct 2025 18:21:52 +0000 Subject: [PATCH 01/12] fix: unset color variables before sourcing .env to prevent ANSI escape sequence errors --- setup.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.sh b/setup.sh index 6cced59..4bd50a7 100755 --- a/setup.sh +++ b/setup.sh @@ -2931,6 +2931,8 @@ update_installation() { # Load existing .env to get database credentials if [ -f "$instance_dir/backend/.env" ]; then + # Unset color variables before sourcing to prevent ANSI escape sequences from leaking into .env + unset RED GREEN YELLOW BLUE NC source "$instance_dir/backend/.env" print_status "Loaded existing configuration" From ac420901a652db517f77b3e3a7c20cfef270254f Mon Sep 17 00:00:00 2001 From: Muhammad Ibrahim Date: Tue, 28 Oct 2025 18:25:13 +0000 Subject: [PATCH 02/12] fix: add try-catch protection for Trianglify canvas generation to prevent runtime errors in Docker --- frontend/src/components/Layout.jsx | 45 +++++++++++++++++------------- frontend/src/pages/Login.jsx | 37 +++++++++++++----------- 2 files changed, 46 insertions(+), 36 deletions(-) diff --git a/frontend/src/components/Layout.jsx b/frontend/src/components/Layout.jsx index 9833914..5032ec1 100644 --- a/frontend/src/components/Layout.jsx +++ b/frontend/src/components/Layout.jsx @@ -240,28 +240,33 @@ const Layout = ({ children }) => { // Generate Trianglify background for dark mode useEffect(() => { const generateBackground = () => { - if ( - bgCanvasRef.current && - themeConfig?.login && - document.documentElement.classList.contains("dark") - ) { - // Get current date as seed for daily variation - const today = new Date(); - const dateSeed = `${today.getFullYear()}-${today.getMonth()}-${today.getDate()}`; + try { + if ( + bgCanvasRef.current && + themeConfig?.login && + document.documentElement.classList.contains("dark") + ) { + // Get current date as seed for daily variation + const today = new Date(); + const dateSeed = `${today.getFullYear()}-${today.getMonth()}-${today.getDate()}`; - // Generate pattern with selected theme configuration - const pattern = trianglify({ - width: window.innerWidth, - height: window.innerHeight, - cellSize: themeConfig.login.cellSize, - variance: themeConfig.login.variance, - seed: dateSeed, - xColors: themeConfig.login.xColors, - yColors: themeConfig.login.yColors, - }); + // Generate pattern with selected theme configuration + const pattern = trianglify({ + width: window.innerWidth, + height: window.innerHeight, + cellSize: themeConfig.login.cellSize, + variance: themeConfig.login.variance, + seed: dateSeed, + xColors: themeConfig.login.xColors, + yColors: themeConfig.login.yColors, + }); - // Render to canvas - pattern.toCanvas(bgCanvasRef.current); + // Render to canvas + pattern.toCanvas(bgCanvasRef.current); + } + } catch (error) { + // Canvas/trianglify not available, skip background generation + console.warn("Could not generate Trianglify background:", error); } }; diff --git a/frontend/src/pages/Login.jsx b/frontend/src/pages/Login.jsx index 03c5054..9979476 100644 --- a/frontend/src/pages/Login.jsx +++ b/frontend/src/pages/Login.jsx @@ -60,24 +60,29 @@ const Login = () => { // Generate Trianglify background based on selected theme useEffect(() => { const generateBackground = () => { - if (canvasRef.current && themeConfig?.login) { - // Get current date as seed for daily variation - const today = new Date(); - const dateSeed = `${today.getFullYear()}-${today.getMonth()}-${today.getDate()}`; + try { + if (canvasRef.current && themeConfig?.login) { + // Get current date as seed for daily variation + const today = new Date(); + const dateSeed = `${today.getFullYear()}-${today.getMonth()}-${today.getDate()}`; - // Generate pattern with selected theme configuration - const pattern = trianglify({ - width: canvasRef.current.offsetWidth, - height: canvasRef.current.offsetHeight, - cellSize: themeConfig.login.cellSize, - variance: themeConfig.login.variance, - seed: dateSeed, - xColors: themeConfig.login.xColors, - yColors: themeConfig.login.yColors, - }); + // Generate pattern with selected theme configuration + const pattern = trianglify({ + width: canvasRef.current.offsetWidth, + height: canvasRef.current.offsetHeight, + cellSize: themeConfig.login.cellSize, + variance: themeConfig.login.variance, + seed: dateSeed, + xColors: themeConfig.login.xColors, + yColors: themeConfig.login.yColors, + }); - // Render to canvas - pattern.toCanvas(canvasRef.current); + // Render to canvas + pattern.toCanvas(canvasRef.current); + } + } catch (error) { + // Canvas/trianglify not available, skip background generation + console.warn("Could not generate Trianglify background:", error); } }; From 3ce8c02a31a3d89c5c5a9b246fa65ebb92d17261 Mon Sep 17 00:00:00 2001 From: Muhammad Ibrahim Date: Tue, 28 Oct 2025 18:30:49 +0000 Subject: [PATCH 03/12] fix: suppress Trianglify errors in production to reduce console noise --- frontend/src/components/Layout.jsx | 7 +++++-- frontend/src/pages/Login.jsx | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/Layout.jsx b/frontend/src/components/Layout.jsx index 5032ec1..a50c65c 100644 --- a/frontend/src/components/Layout.jsx +++ b/frontend/src/components/Layout.jsx @@ -265,8 +265,11 @@ const Layout = ({ children }) => { pattern.toCanvas(bgCanvasRef.current); } } catch (error) { - // Canvas/trianglify not available, skip background generation - console.warn("Could not generate Trianglify background:", error); + // Canvas/trianglify not available, skip background generation silently + // Only log in development for debugging + if (import.meta.env.DEV) { + console.warn("Could not generate Trianglify background:", error); + } } }; diff --git a/frontend/src/pages/Login.jsx b/frontend/src/pages/Login.jsx index 9979476..42d37a3 100644 --- a/frontend/src/pages/Login.jsx +++ b/frontend/src/pages/Login.jsx @@ -81,8 +81,11 @@ const Login = () => { pattern.toCanvas(canvasRef.current); } } catch (error) { - // Canvas/trianglify not available, skip background generation - console.warn("Could not generate Trianglify background:", error); + // Canvas/trianglify not available, skip background generation silently + // Only log in development for debugging + if (import.meta.env.DEV) { + console.warn("Could not generate Trianglify background:", error); + } } }; From e9368d1a95e33530fa68e810f04ccd07103db285 Mon Sep 17 00:00:00 2001 From: Muhammad Ibrahim Date: Tue, 28 Oct 2025 18:31:55 +0000 Subject: [PATCH 04/12] feat: add canvas runtime dependencies to frontend Docker image for Trianglify support --- docker/frontend.Dockerfile | 3 +++ frontend/src/components/Layout.jsx | 7 ++----- frontend/src/pages/Login.jsx | 7 ++----- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/docker/frontend.Dockerfile b/docker/frontend.Dockerfile index 1301aec..5a1c702 100644 --- a/docker/frontend.Dockerfile +++ b/docker/frontend.Dockerfile @@ -39,6 +39,9 @@ RUN npm run build # Production stage FROM nginxinc/nginx-unprivileged:alpine +# Install runtime dependencies for canvas +RUN apk add --no-cache cairo pango jpeg libpng giflib + ENV BACKEND_HOST=backend \ BACKEND_PORT=3001 diff --git a/frontend/src/components/Layout.jsx b/frontend/src/components/Layout.jsx index a50c65c..5032ec1 100644 --- a/frontend/src/components/Layout.jsx +++ b/frontend/src/components/Layout.jsx @@ -265,11 +265,8 @@ const Layout = ({ children }) => { pattern.toCanvas(bgCanvasRef.current); } } catch (error) { - // Canvas/trianglify not available, skip background generation silently - // Only log in development for debugging - if (import.meta.env.DEV) { - console.warn("Could not generate Trianglify background:", error); - } + // Canvas/trianglify not available, skip background generation + console.warn("Could not generate Trianglify background:", error); } }; diff --git a/frontend/src/pages/Login.jsx b/frontend/src/pages/Login.jsx index 42d37a3..9979476 100644 --- a/frontend/src/pages/Login.jsx +++ b/frontend/src/pages/Login.jsx @@ -81,11 +81,8 @@ const Login = () => { pattern.toCanvas(canvasRef.current); } } catch (error) { - // Canvas/trianglify not available, skip background generation silently - // Only log in development for debugging - if (import.meta.env.DEV) { - console.warn("Could not generate Trianglify background:", error); - } + // Canvas/trianglify not available, skip background generation + console.warn("Could not generate Trianglify background:", error); } }; From 43fb54a683aa899426e3d9934bf4ca2995be7582 Mon Sep 17 00:00:00 2001 From: Muhammad Ibrahim Date: Tue, 28 Oct 2025 18:33:43 +0000 Subject: [PATCH 05/12] fix: change nginx-unprivileged to nginx:alpine for canvas runtime dependencies --- docker/frontend.Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/frontend.Dockerfile b/docker/frontend.Dockerfile index 5a1c702..13ac55c 100644 --- a/docker/frontend.Dockerfile +++ b/docker/frontend.Dockerfile @@ -36,8 +36,8 @@ COPY frontend/ ./ RUN npm run build -# Production stage -FROM nginxinc/nginx-unprivileged:alpine +# Production stage - use standard nginx to allow apk install +FROM nginx:alpine # Install runtime dependencies for canvas RUN apk add --no-cache cairo pango jpeg libpng giflib From 93760d03e128d88c172968c13a3789c0352d4d5e Mon Sep 17 00:00:00 2001 From: Muhammad Ibrahim Date: Tue, 28 Oct 2025 18:35:03 +0000 Subject: [PATCH 06/12] feat: maintain nginx-unprivileged security while adding canvas runtime libraries via multi-stage build --- docker/frontend.Dockerfile | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/docker/frontend.Dockerfile b/docker/frontend.Dockerfile index 13ac55c..36765be 100644 --- a/docker/frontend.Dockerfile +++ b/docker/frontend.Dockerfile @@ -36,12 +36,24 @@ COPY frontend/ ./ RUN npm run build -# Production stage - use standard nginx to allow apk install -FROM nginx:alpine +# Production stage - use temporary stage to install packages as root, then copy to unprivileged +FROM nginx:alpine AS runtime-builder # Install runtime dependencies for canvas RUN apk add --no-cache cairo pango jpeg libpng giflib +# Final production stage - unprivileged +FROM nginxinc/nginx-unprivileged:alpine + +# Copy runtime libraries from runtime-builder +COPY --from=runtime-builder /usr/lib/libcairo.so.2 /usr/lib/ +COPY --from=runtime-builder /usr/lib/libpango-1.0.so.0 /usr/lib/ +COPY --from=runtime-builder /usr/lib/libpangocairo-1.0.so.0 /usr/lib/ +COPY --from=runtime-builder /usr/lib/libpangoft2-1.0.so.0 /usr/lib/ +COPY --from=runtime-builder /usr/lib/libpng16.so.16 /usr/lib/ +COPY --from=runtime-builder /usr/lib/libgif.so.7 /usr/lib/ +COPY --from=runtime-builder /usr/lib/libjpeg.so.8 /usr/lib/ + ENV BACKEND_HOST=backend \ BACKEND_PORT=3001 From 2975da0f69750eeccb68b800ee9bd4383172a62e Mon Sep 17 00:00:00 2001 From: Muhammad Ibrahim Date: Tue, 28 Oct 2025 18:35:52 +0000 Subject: [PATCH 07/12] fix: use SVG-based rendering for Trianglify in browser instead of Node.js canvas --- frontend/src/components/Layout.jsx | 19 +++++++++++++++++-- frontend/src/pages/Login.jsx | 19 +++++++++++++++++-- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/Layout.jsx b/frontend/src/components/Layout.jsx index 5032ec1..1e800e8 100644 --- a/frontend/src/components/Layout.jsx +++ b/frontend/src/components/Layout.jsx @@ -261,8 +261,23 @@ const Layout = ({ children }) => { yColors: themeConfig.login.yColors, }); - // Render to canvas - pattern.toCanvas(bgCanvasRef.current); + // Render to canvas using SVG (browser-compatible) + const svg = pattern.toSVG(); + const ctx = bgCanvasRef.current.getContext("2d"); + const img = new Image(); + const blob = new Blob([svg], { type: "image/svg+xml" }); + const url = URL.createObjectURL(blob); + img.onload = () => { + ctx.drawImage( + img, + 0, + 0, + bgCanvasRef.current.width, + bgCanvasRef.current.height, + ); + URL.revokeObjectURL(url); + }; + img.src = url; } } catch (error) { // Canvas/trianglify not available, skip background generation diff --git a/frontend/src/pages/Login.jsx b/frontend/src/pages/Login.jsx index 9979476..00705a1 100644 --- a/frontend/src/pages/Login.jsx +++ b/frontend/src/pages/Login.jsx @@ -77,8 +77,23 @@ const Login = () => { yColors: themeConfig.login.yColors, }); - // Render to canvas - pattern.toCanvas(canvasRef.current); + // Render to canvas using SVG (browser-compatible) + const svg = pattern.toSVG(); + const ctx = canvasRef.current.getContext("2d"); + const img = new Image(); + const blob = new Blob([svg], { type: "image/svg+xml" }); + const url = URL.createObjectURL(blob); + img.onload = () => { + ctx.drawImage( + img, + 0, + 0, + canvasRef.current.width, + canvasRef.current.height, + ); + URL.revokeObjectURL(url); + }; + img.src = url; } } catch (error) { // Canvas/trianglify not available, skip background generation From 1d2c003830d565ec1c8e8473266cb71ef3fbe67d Mon Sep 17 00:00:00 2001 From: Muhammad Ibrahim Date: Tue, 28 Oct 2025 18:47:56 +0000 Subject: [PATCH 08/12] fix: replace trianglify with pure browser gradient mesh generator - Removed trianglify dependency (which required Node.js canvas) - Implemented custom gradient mesh using HTML5 Canvas API - Browser-native solution works in Docker without native dependencies - Maintains daily variation and theme color support - No more 'i.from is not a function' errors --- docker/frontend.Dockerfile | 24 +- frontend/package.json | 3 +- frontend/src/components/Layout.jsx | 103 ++++--- frontend/src/pages/Login.jsx | 95 ++++--- package-lock.json | 428 +---------------------------- 5 files changed, 129 insertions(+), 524 deletions(-) diff --git a/docker/frontend.Dockerfile b/docker/frontend.Dockerfile index 36765be..68f7a64 100644 --- a/docker/frontend.Dockerfile +++ b/docker/frontend.Dockerfile @@ -17,9 +17,6 @@ CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0", "--port", "3000"] # Builder stage for production FROM node:lts-alpine AS builder -# Install build dependencies for canvas -RUN apk add --no-cache python3 make g++ pkgconfig cairo-dev jpeg-dev pango-dev giflib-dev - WORKDIR /app/frontend COPY frontend/package*.json ./ @@ -27,8 +24,8 @@ COPY frontend/package*.json ./ RUN echo "=== Starting npm install ===" &&\ npm cache clean --force &&\ rm -rf node_modules ~/.npm /root/.npm &&\ - echo "=== npm install with verbose logging ===" &&\ - npm install --legacy-peer-deps --no-audit --prefer-online --fetch-retries=3 --fetch-retry-mintimeout=20000 --fetch-retry-maxtimeout=120000 --loglevel=verbose &&\ + echo "=== npm install ===" &&\ + npm install --ignore-scripts --legacy-peer-deps --no-audit --prefer-online --fetch-retries=3 --fetch-retry-mintimeout=20000 --fetch-retry-maxtimeout=120000 &&\ echo "=== npm install completed ===" &&\ npm cache clean --force @@ -36,24 +33,9 @@ COPY frontend/ ./ RUN npm run build -# Production stage - use temporary stage to install packages as root, then copy to unprivileged -FROM nginx:alpine AS runtime-builder - -# Install runtime dependencies for canvas -RUN apk add --no-cache cairo pango jpeg libpng giflib - -# Final production stage - unprivileged +# Production stage FROM nginxinc/nginx-unprivileged:alpine -# Copy runtime libraries from runtime-builder -COPY --from=runtime-builder /usr/lib/libcairo.so.2 /usr/lib/ -COPY --from=runtime-builder /usr/lib/libpango-1.0.so.0 /usr/lib/ -COPY --from=runtime-builder /usr/lib/libpangocairo-1.0.so.0 /usr/lib/ -COPY --from=runtime-builder /usr/lib/libpangoft2-1.0.so.0 /usr/lib/ -COPY --from=runtime-builder /usr/lib/libpng16.so.16 /usr/lib/ -COPY --from=runtime-builder /usr/lib/libgif.so.7 /usr/lib/ -COPY --from=runtime-builder /usr/lib/libjpeg.so.8 /usr/lib/ - ENV BACKEND_HOST=backend \ BACKEND_PORT=3001 diff --git a/frontend/package.json b/frontend/package.json index fc4443e..1feada8 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -27,8 +27,7 @@ "react-chartjs-2": "^5.2.0", "react-dom": "^18.3.1", "react-icons": "^5.5.0", - "react-router-dom": "^6.30.1", - "trianglify": "^4.1.1" + "react-router-dom": "^6.30.1" }, "devDependencies": { "@types/react": "^18.3.14", diff --git a/frontend/src/components/Layout.jsx b/frontend/src/components/Layout.jsx index 1e800e8..50b9d0e 100644 --- a/frontend/src/components/Layout.jsx +++ b/frontend/src/components/Layout.jsx @@ -28,7 +28,6 @@ import { import { useCallback, useEffect, useRef, useState } from "react"; import { FaReddit, FaYoutube } from "react-icons/fa"; import { Link, useLocation, useNavigate } from "react-router-dom"; -import trianglify from "trianglify"; import { useAuth } from "../contexts/AuthContext"; import { useColorTheme } from "../contexts/ColorThemeContext"; import { useUpdateNotification } from "../contexts/UpdateNotificationContext"; @@ -237,51 +236,73 @@ const Layout = ({ children }) => { navigate("/hosts?action=add"); }; - // Generate Trianglify background for dark mode + // Generate geometric background pattern for dark mode useEffect(() => { const generateBackground = () => { - try { - if ( - bgCanvasRef.current && - themeConfig?.login && - document.documentElement.classList.contains("dark") - ) { - // Get current date as seed for daily variation - const today = new Date(); - const dateSeed = `${today.getFullYear()}-${today.getMonth()}-${today.getDate()}`; + if ( + !bgCanvasRef.current || + !themeConfig?.login || + !document.documentElement.classList.contains("dark") + ) { + return; + } - // Generate pattern with selected theme configuration - const pattern = trianglify({ - width: window.innerWidth, - height: window.innerHeight, - cellSize: themeConfig.login.cellSize, - variance: themeConfig.login.variance, - seed: dateSeed, - xColors: themeConfig.login.xColors, - yColors: themeConfig.login.yColors, - }); + const canvas = bgCanvasRef.current; + canvas.width = window.innerWidth; + canvas.height = window.innerHeight; + const ctx = canvas.getContext("2d"); - // Render to canvas using SVG (browser-compatible) - const svg = pattern.toSVG(); - const ctx = bgCanvasRef.current.getContext("2d"); - const img = new Image(); - const blob = new Blob([svg], { type: "image/svg+xml" }); - const url = URL.createObjectURL(blob); - img.onload = () => { - ctx.drawImage( - img, - 0, - 0, - bgCanvasRef.current.width, - bgCanvasRef.current.height, - ); - URL.revokeObjectURL(url); - }; - img.src = url; + // Get theme colors + const xColors = themeConfig.login.xColors || [ + "#667eea", + "#764ba2", + "#f093fb", + "#4facfe", + ]; + const yColors = themeConfig.login.yColors || [ + "#667eea", + "#764ba2", + "#f093fb", + "#4facfe", + ]; + + // Use date for daily variation + const today = new Date(); + const seed = + today.getFullYear() * 10000 + today.getMonth() * 100 + today.getDate(); + + // Simple seeded random + const random = (s) => { + const x = Math.sin(s) * 10000; + return x - Math.floor(x); + }; + + // Draw gradient mesh + const cellSize = themeConfig.login.cellSize || 75; + const cols = Math.ceil(canvas.width / cellSize) + 1; + const rows = Math.ceil(canvas.height / cellSize) + 1; + + for (let y = 0; y < rows; y++) { + for (let x = 0; x < cols; x++) { + const idx = y * cols + x; + const color1 = + xColors[Math.floor(random(seed + idx) * xColors.length)]; + const color2 = + yColors[Math.floor(random(seed + idx + 1000) * yColors.length)]; + + // Create gradient + const gradient = ctx.createLinearGradient( + x * cellSize, + y * cellSize, + (x + 1) * cellSize, + (y + 1) * cellSize, + ); + gradient.addColorStop(0, color1); + gradient.addColorStop(1, color2); + + ctx.fillStyle = gradient; + ctx.fillRect(x * cellSize, y * cellSize, cellSize, cellSize); } - } catch (error) { - // Canvas/trianglify not available, skip background generation - console.warn("Could not generate Trianglify background:", error); } }; diff --git a/frontend/src/pages/Login.jsx b/frontend/src/pages/Login.jsx index 00705a1..83b52b5 100644 --- a/frontend/src/pages/Login.jsx +++ b/frontend/src/pages/Login.jsx @@ -17,7 +17,6 @@ import { useEffect, useId, useRef, useState } from "react"; import { FaReddit, FaYoutube } from "react-icons/fa"; import { useNavigate } from "react-router-dom"; -import trianglify from "trianglify"; import DiscordIcon from "../components/DiscordIcon"; import { useAuth } from "../contexts/AuthContext"; import { useColorTheme } from "../contexts/ColorThemeContext"; @@ -57,47 +56,67 @@ const Login = () => { const navigate = useNavigate(); - // Generate Trianglify background based on selected theme + // Generate geometric background pattern based on selected theme useEffect(() => { const generateBackground = () => { - try { - if (canvasRef.current && themeConfig?.login) { - // Get current date as seed for daily variation - const today = new Date(); - const dateSeed = `${today.getFullYear()}-${today.getMonth()}-${today.getDate()}`; + if (!canvasRef.current || !themeConfig?.login) return; - // Generate pattern with selected theme configuration - const pattern = trianglify({ - width: canvasRef.current.offsetWidth, - height: canvasRef.current.offsetHeight, - cellSize: themeConfig.login.cellSize, - variance: themeConfig.login.variance, - seed: dateSeed, - xColors: themeConfig.login.xColors, - yColors: themeConfig.login.yColors, - }); + const canvas = canvasRef.current; + canvas.width = canvas.offsetWidth; + canvas.height = canvas.offsetHeight; + const ctx = canvas.getContext("2d"); - // Render to canvas using SVG (browser-compatible) - const svg = pattern.toSVG(); - const ctx = canvasRef.current.getContext("2d"); - const img = new Image(); - const blob = new Blob([svg], { type: "image/svg+xml" }); - const url = URL.createObjectURL(blob); - img.onload = () => { - ctx.drawImage( - img, - 0, - 0, - canvasRef.current.width, - canvasRef.current.height, - ); - URL.revokeObjectURL(url); - }; - img.src = url; + // Get theme colors + const xColors = themeConfig.login.xColors || [ + "#667eea", + "#764ba2", + "#f093fb", + "#4facfe", + ]; + const yColors = themeConfig.login.yColors || [ + "#667eea", + "#764ba2", + "#f093fb", + "#4facfe", + ]; + + // Use date for daily variation + const today = new Date(); + const seed = + today.getFullYear() * 10000 + today.getMonth() * 100 + today.getDate(); + + // Simple seeded random + const random = (s) => { + const x = Math.sin(s) * 10000; + return x - Math.floor(x); + }; + + // Draw gradient mesh + const cellSize = themeConfig.login.cellSize || 75; + const cols = Math.ceil(canvas.width / cellSize) + 1; + const rows = Math.ceil(canvas.height / cellSize) + 1; + + for (let y = 0; y < rows; y++) { + for (let x = 0; x < cols; x++) { + const idx = y * cols + x; + const color1 = + xColors[Math.floor(random(seed + idx) * xColors.length)]; + const color2 = + yColors[Math.floor(random(seed + idx + 1000) * yColors.length)]; + + // Create gradient + const gradient = ctx.createLinearGradient( + x * cellSize, + y * cellSize, + (x + 1) * cellSize, + (y + 1) * cellSize, + ); + gradient.addColorStop(0, color1); + gradient.addColorStop(1, color2); + + ctx.fillStyle = gradient; + ctx.fillRect(x * cellSize, y * cellSize, cellSize, cellSize); } - } catch (error) { - // Canvas/trianglify not available, skip background generation - console.warn("Could not generate Trianglify background:", error); } }; @@ -110,7 +129,7 @@ const Login = () => { window.addEventListener("resize", handleResize); return () => window.removeEventListener("resize", handleResize); - }, [themeConfig]); // Regenerate when theme changes + }, [themeConfig]); // Check if signup is enabled useEffect(() => { diff --git a/package-lock.json b/package-lock.json index 8067f65..fb91b82 100644 --- a/package-lock.json +++ b/package-lock.json @@ -78,8 +78,7 @@ "react-chartjs-2": "^5.2.0", "react-dom": "^18.3.1", "react-icons": "^5.5.0", - "react-router-dom": "^6.30.1", - "trianglify": "^4.1.1" + "react-router-dom": "^6.30.1" }, "devDependencies": { "@types/react": "^18.3.14", @@ -642,34 +641,6 @@ "version": "0.3.4", "license": "MIT" }, - "node_modules/@mapbox/node-pre-gyp": { - "version": "1.0.11", - "license": "BSD-3-Clause", - "dependencies": { - "detect-libc": "^2.0.0", - "https-proxy-agent": "^5.0.0", - "make-dir": "^3.1.0", - "node-fetch": "^2.6.7", - "nopt": "^5.0.0", - "npmlog": "^5.0.1", - "rimraf": "^3.0.2", - "semver": "^7.3.5", - "tar": "^6.1.11" - }, - "bin": { - "node-pre-gyp": "bin/node-pre-gyp" - } - }, - "node_modules/@mapbox/node-pre-gyp/node_modules/semver": { - "version": "7.7.3", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": { "version": "3.0.3", "cpu": [ @@ -962,10 +933,6 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, - "node_modules/abbrev": { - "version": "1.1.1", - "license": "ISC" - }, "node_modules/accepts": { "version": "1.3.8", "license": "MIT", @@ -977,16 +944,6 @@ "node": ">= 0.6" } }, - "node_modules/agent-base": { - "version": "6.0.2", - "license": "MIT", - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, "node_modules/ansi-regex": { "version": "5.0.1", "license": "MIT", @@ -1024,21 +981,6 @@ "node": ">= 8" } }, - "node_modules/aproba": { - "version": "2.1.0", - "license": "ISC" - }, - "node_modules/are-we-there-yet": { - "version": "2.0.0", - "license": "ISC", - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/arg": { "version": "5.0.2", "dev": true, @@ -1167,6 +1109,7 @@ }, "node_modules/brace-expansion": { "version": "1.1.12", + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -1361,19 +1304,6 @@ ], "license": "CC-BY-4.0" }, - "node_modules/canvas": { - "version": "2.11.2", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "@mapbox/node-pre-gyp": "^1.0.0", - "nan": "^2.17.0", - "simple-get": "^3.0.3" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/chalk": { "version": "4.1.2", "dev": true, @@ -1433,17 +1363,6 @@ "fsevents": "~2.3.2" } }, - "node_modules/chownr": { - "version": "2.0.0", - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/chroma-js": { - "version": "2.6.0", - "license": "(BSD-3-Clause AND Apache-2.0)" - }, "node_modules/citty": { "version": "0.1.6", "devOptional": true, @@ -1509,13 +1428,6 @@ "simple-swizzle": "^0.2.2" } }, - "node_modules/color-support": { - "version": "1.1.3", - "license": "ISC", - "bin": { - "color-support": "bin.js" - } - }, "node_modules/color/node_modules/color-convert": { "version": "1.9.3", "license": "MIT", @@ -1555,6 +1467,7 @@ }, "node_modules/concat-map": { "version": "0.0.1", + "dev": true, "license": "MIT" }, "node_modules/concurrently": { @@ -1596,10 +1509,6 @@ "node": "^14.18.0 || >=16.10.0" } }, - "node_modules/console-control-strings": { - "version": "1.1.0", - "license": "ISC" - }, "node_modules/content-disposition": { "version": "0.5.4", "license": "MIT", @@ -1738,16 +1647,6 @@ "node": ">=0.10.0" } }, - "node_modules/decompress-response": { - "version": "4.2.1", - "license": "MIT", - "dependencies": { - "mimic-response": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/deepmerge-ts": { "version": "7.1.5", "devOptional": true, @@ -1761,10 +1660,6 @@ "devOptional": true, "license": "MIT" }, - "node_modules/delaunator": { - "version": "4.0.1", - "license": "ISC" - }, "node_modules/delayed-stream": { "version": "1.0.0", "license": "MIT", @@ -1772,10 +1667,6 @@ "node": ">=0.4.0" } }, - "node_modules/delegates": { - "version": "1.0.0", - "license": "MIT" - }, "node_modules/denque": { "version": "2.1.0", "license": "Apache-2.0", @@ -1806,6 +1697,7 @@ "node_modules/detect-libc": { "version": "2.1.2", "license": "Apache-2.0", + "optional": true, "engines": { "node": ">=8" } @@ -2293,34 +2185,6 @@ "node": ">= 0.6" } }, - "node_modules/fs-minipass": { - "version": "2.1.0", - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fs-minipass/node_modules/yallist": { - "version": "4.0.0", - "license": "ISC" - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "license": "ISC" - }, "node_modules/function-bind": { "version": "1.1.2", "license": "MIT", @@ -2328,28 +2192,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/gauge": { - "version": "3.0.2", - "license": "ISC", - "dependencies": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.2", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.1", - "object-assign": "^4.1.1", - "signal-exit": "^3.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/gauge/node_modules/signal-exit": { - "version": "3.0.7", - "license": "ISC" - }, "node_modules/gensync": { "version": "1.0.0-beta.2", "dev": true, @@ -2507,10 +2349,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-unicode": { - "version": "2.0.1", - "license": "ISC" - }, "node_modules/hasown": { "version": "2.0.2", "license": "MIT", @@ -2569,17 +2407,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/https-proxy-agent": { - "version": "5.0.1", - "license": "MIT", - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/iconv-lite": { "version": "0.4.24", "license": "MIT", @@ -2595,14 +2422,6 @@ "dev": true, "license": "ISC" }, - "node_modules/inflight": { - "version": "1.0.6", - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, "node_modules/inherits": { "version": "2.0.4", "license": "ISC" @@ -2978,19 +2797,6 @@ "node": ">=12" } }, - "node_modules/make-dir": { - "version": "3.1.0", - "license": "MIT", - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/math-intrinsics": { "version": "1.1.0", "license": "MIT", @@ -3065,18 +2871,9 @@ "node": ">= 0.6" } }, - "node_modules/mimic-response": { - "version": "2.1.0", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/minimatch": { "version": "3.1.2", + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -3093,41 +2890,6 @@ "node": ">=16 || 14 >=14.17" } }, - "node_modules/minizlib": { - "version": "2.1.2", - "license": "MIT", - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minizlib/node_modules/yallist": { - "version": "4.0.0", - "license": "ISC" - }, - "node_modules/mkdirp": { - "version": "1.0.4", - "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/moment": { "version": "2.30.1", "license": "MIT", @@ -3176,10 +2938,6 @@ "thenify-all": "^1.0.0" } }, - "node_modules/nan": { - "version": "2.23.0", - "license": "MIT" - }, "node_modules/nanoid": { "version": "3.3.11", "dev": true, @@ -3208,24 +2966,6 @@ "version": "3.1.1", "license": "MIT" }, - "node_modules/node-fetch": { - "version": "2.7.0", - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, "node_modules/node-fetch-native": { "version": "1.6.7", "devOptional": true, @@ -3306,19 +3046,6 @@ "node": ">=4" } }, - "node_modules/nopt": { - "version": "5.0.0", - "license": "ISC", - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/normalize-path": { "version": "3.0.0", "dev": true, @@ -3335,16 +3062,6 @@ "node": ">=0.10.0" } }, - "node_modules/npmlog": { - "version": "5.0.1", - "license": "ISC", - "dependencies": { - "are-we-there-yet": "^2.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^3.0.0", - "set-blocking": "^2.0.0" - } - }, "node_modules/nypm": { "version": "0.6.2", "devOptional": true, @@ -3403,13 +3120,6 @@ "node": ">= 0.8" } }, - "node_modules/once": { - "version": "1.4.0", - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, "node_modules/one-time": { "version": "1.0.0", "license": "MIT", @@ -3474,13 +3184,6 @@ "node": ">=8" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/path-key": { "version": "3.1.1", "dev": true, @@ -4078,37 +3781,6 @@ "node": ">=0.10.0" } }, - "node_modules/rimraf": { - "version": "3.0.2", - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/glob": { - "version": "7.2.3", - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/rollup": { "version": "4.52.3", "dev": true, @@ -4217,6 +3889,7 @@ }, "node_modules/semver": { "version": "6.3.1", + "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -4388,33 +4061,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/simple-concat": { - "version": "1.0.1", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/simple-get": { - "version": "3.1.1", - "license": "MIT", - "dependencies": { - "decompress-response": "^4.2.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" - } - }, "node_modules/simple-swizzle": { "version": "0.2.4", "license": "MIT", @@ -4640,32 +4286,6 @@ "jiti": "bin/jiti.js" } }, - "node_modules/tar": { - "version": "6.2.1", - "license": "ISC", - "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/tar/node_modules/minipass": { - "version": "5.0.0", - "license": "ISC", - "engines": { - "node": ">=8" - } - }, - "node_modules/tar/node_modules/yallist": { - "version": "4.0.0", - "license": "ISC" - }, "node_modules/text-hex": { "version": "1.0.0", "license": "MIT" @@ -4761,10 +4381,6 @@ "nodetouch": "bin/nodetouch.js" } }, - "node_modules/tr46": { - "version": "0.0.3", - "license": "MIT" - }, "node_modules/tree-kill": { "version": "1.2.2", "dev": true, @@ -4773,15 +4389,6 @@ "tree-kill": "cli.js" } }, - "node_modules/trianglify": { - "version": "4.1.1", - "license": "GPL-3.0", - "dependencies": { - "canvas": "^2.6.1", - "chroma-js": "^2.1.0", - "delaunator": "^4.0.1" - } - }, "node_modules/triple-beam": { "version": "1.4.1", "license": "MIT", @@ -4990,18 +4597,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "license": "BSD-2-Clause" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/which": { "version": "2.0.2", "dev": true, @@ -5020,13 +4615,6 @@ "version": "2.0.1", "license": "ISC" }, - "node_modules/wide-align": { - "version": "1.1.5", - "license": "ISC", - "dependencies": { - "string-width": "^1.0.2 || 2 || 3 || 4" - } - }, "node_modules/winston": { "version": "3.17.0", "license": "MIT", @@ -5092,10 +4680,6 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wrappy": { - "version": "1.0.2", - "license": "ISC" - }, "node_modules/ws": { "version": "8.18.3", "license": "MIT", From f85721b292bbbe0db8ffca42c505f906fc2d08ec Mon Sep 17 00:00:00 2001 From: Muhammad Ibrahim Date: Tue, 28 Oct 2025 18:49:39 +0000 Subject: [PATCH 09/12] feat: improve background with low-poly triangular pattern - Replaced square grid with triangular mesh for more organic look - Increased default cell size from 75px to 150px for subtlety - Added variance to point positions for natural randomness - Each cell now renders two triangles with gradient fills - Maintains theme colors and daily variation --- frontend/src/components/Layout.jsx | 78 +++++++++++++++++++++--------- frontend/src/pages/Login.jsx | 78 +++++++++++++++++++++--------- 2 files changed, 112 insertions(+), 44 deletions(-) diff --git a/frontend/src/components/Layout.jsx b/frontend/src/components/Layout.jsx index 50b9d0e..89bf5e4 100644 --- a/frontend/src/components/Layout.jsx +++ b/frontend/src/components/Layout.jsx @@ -236,7 +236,7 @@ const Layout = ({ children }) => { navigate("/hosts?action=add"); }; - // Generate geometric background pattern for dark mode + // Generate low-poly triangular background pattern for dark mode useEffect(() => { const generateBackground = () => { if ( @@ -265,6 +265,7 @@ const Layout = ({ children }) => { "#f093fb", "#4facfe", ]; + const allColors = [...xColors, ...yColors]; // Use date for daily variation const today = new Date(); @@ -277,31 +278,64 @@ const Layout = ({ children }) => { return x - Math.floor(x); }; - // Draw gradient mesh - const cellSize = themeConfig.login.cellSize || 75; - const cols = Math.ceil(canvas.width / cellSize) + 1; - const rows = Math.ceil(canvas.height / cellSize) + 1; + // Create a grid of points with some variance + const cellSize = themeConfig.login.cellSize || 150; // Larger cells for more subtle effect + const variance = themeConfig.login.variance || 0.5; + const cols = Math.ceil(canvas.width / cellSize) + 2; + const rows = Math.ceil(canvas.height / cellSize) + 2; - for (let y = 0; y < rows; y++) { - for (let x = 0; x < cols; x++) { + const points = []; + for (let y = -1; y < rows; y++) { + for (let x = -1; x < cols; x++) { const idx = y * cols + x; - const color1 = - xColors[Math.floor(random(seed + idx) * xColors.length)]; - const color2 = - yColors[Math.floor(random(seed + idx + 1000) * yColors.length)]; + const offsetX = (random(seed + idx * 2) - 0.5) * cellSize * variance; + const offsetY = + (random(seed + idx * 2 + 1000) - 0.5) * cellSize * variance; + points.push({ + x: x * cellSize + offsetX, + y: y * cellSize + offsetY, + color: allColors[Math.floor(random(seed + idx) * allColors.length)], + }); + } + } - // Create gradient - const gradient = ctx.createLinearGradient( - x * cellSize, - y * cellSize, - (x + 1) * cellSize, - (y + 1) * cellSize, - ); - gradient.addColorStop(0, color1); - gradient.addColorStop(1, color2); + // Draw triangles between points + for (let y = 0; y < rows - 1; y++) { + for (let x = 0; x < cols - 1; x++) { + const idx = y * cols + x; + const p1 = points[idx]; + const p2 = points[idx + 1]; + const p3 = points[idx + cols]; + const p4 = points[idx + cols + 1]; - ctx.fillStyle = gradient; - ctx.fillRect(x * cellSize, y * cellSize, cellSize, cellSize); + // Draw two triangles per cell + // Triangle 1 + ctx.beginPath(); + ctx.moveTo(p1.x, p1.y); + ctx.lineTo(p2.x, p2.y); + ctx.lineTo(p3.x, p3.y); + ctx.closePath(); + + const gradient1 = ctx.createLinearGradient(p1.x, p1.y, p2.x, p2.y); + gradient1.addColorStop(0, p1.color); + gradient1.addColorStop(0.5, p2.color); + gradient1.addColorStop(1, p3.color); + ctx.fillStyle = gradient1; + ctx.fill(); + + // Triangle 2 + ctx.beginPath(); + ctx.moveTo(p2.x, p2.y); + ctx.lineTo(p4.x, p4.y); + ctx.lineTo(p3.x, p3.y); + ctx.closePath(); + + const gradient2 = ctx.createLinearGradient(p2.x, p2.y, p4.x, p4.y); + gradient2.addColorStop(0, p2.color); + gradient2.addColorStop(0.5, p4.color); + gradient2.addColorStop(1, p3.color); + ctx.fillStyle = gradient2; + ctx.fill(); } } }; diff --git a/frontend/src/pages/Login.jsx b/frontend/src/pages/Login.jsx index 83b52b5..e10ef67 100644 --- a/frontend/src/pages/Login.jsx +++ b/frontend/src/pages/Login.jsx @@ -56,7 +56,7 @@ const Login = () => { const navigate = useNavigate(); - // Generate geometric background pattern based on selected theme + // Generate low-poly triangular background pattern based on selected theme useEffect(() => { const generateBackground = () => { if (!canvasRef.current || !themeConfig?.login) return; @@ -79,6 +79,7 @@ const Login = () => { "#f093fb", "#4facfe", ]; + const allColors = [...xColors, ...yColors]; // Use date for daily variation const today = new Date(); @@ -91,31 +92,64 @@ const Login = () => { return x - Math.floor(x); }; - // Draw gradient mesh - const cellSize = themeConfig.login.cellSize || 75; - const cols = Math.ceil(canvas.width / cellSize) + 1; - const rows = Math.ceil(canvas.height / cellSize) + 1; + // Create a grid of points with some variance + const cellSize = themeConfig.login.cellSize || 150; // Larger cells for more subtle effect + const variance = themeConfig.login.variance || 0.5; + const cols = Math.ceil(canvas.width / cellSize) + 2; + const rows = Math.ceil(canvas.height / cellSize) + 2; - for (let y = 0; y < rows; y++) { - for (let x = 0; x < cols; x++) { + const points = []; + for (let y = -1; y < rows; y++) { + for (let x = -1; x < cols; x++) { const idx = y * cols + x; - const color1 = - xColors[Math.floor(random(seed + idx) * xColors.length)]; - const color2 = - yColors[Math.floor(random(seed + idx + 1000) * yColors.length)]; + const offsetX = (random(seed + idx * 2) - 0.5) * cellSize * variance; + const offsetY = + (random(seed + idx * 2 + 1000) - 0.5) * cellSize * variance; + points.push({ + x: x * cellSize + offsetX, + y: y * cellSize + offsetY, + color: allColors[Math.floor(random(seed + idx) * allColors.length)], + }); + } + } - // Create gradient - const gradient = ctx.createLinearGradient( - x * cellSize, - y * cellSize, - (x + 1) * cellSize, - (y + 1) * cellSize, - ); - gradient.addColorStop(0, color1); - gradient.addColorStop(1, color2); + // Draw triangles between points + for (let y = 0; y < rows - 1; y++) { + for (let x = 0; x < cols - 1; x++) { + const idx = y * cols + x; + const p1 = points[idx]; + const p2 = points[idx + 1]; + const p3 = points[idx + cols]; + const p4 = points[idx + cols + 1]; - ctx.fillStyle = gradient; - ctx.fillRect(x * cellSize, y * cellSize, cellSize, cellSize); + // Draw two triangles per cell + // Triangle 1 + ctx.beginPath(); + ctx.moveTo(p1.x, p1.y); + ctx.lineTo(p2.x, p2.y); + ctx.lineTo(p3.x, p3.y); + ctx.closePath(); + + const gradient1 = ctx.createLinearGradient(p1.x, p1.y, p2.x, p2.y); + gradient1.addColorStop(0, p1.color); + gradient1.addColorStop(0.5, p2.color); + gradient1.addColorStop(1, p3.color); + ctx.fillStyle = gradient1; + ctx.fill(); + + // Triangle 2 + ctx.beginPath(); + ctx.moveTo(p2.x, p2.y); + ctx.lineTo(p4.x, p4.y); + ctx.lineTo(p3.x, p3.y); + ctx.closePath(); + + const gradient2 = ctx.createLinearGradient(p2.x, p2.y, p4.x, p4.y); + gradient2.addColorStop(0, p2.color); + gradient2.addColorStop(0.5, p4.color); + gradient2.addColorStop(1, p3.color); + ctx.fillStyle = gradient2; + ctx.fill(); } } }; From 2ec2b3992c9a174ea0bae372371974fbeb589713 Mon Sep 17 00:00:00 2001 From: Muhammad Ibrahim Date: Tue, 28 Oct 2025 18:52:00 +0000 Subject: [PATCH 10/12] feat: replace with clean radial gradient background - Removed busy triangular pattern - Clean, subtle radial gradient like modern Linux wallpapers - Light center (top-left) flowing to darker bottom-right corner - Uses theme colors but much more minimalist - Daily color rotation maintained --- frontend/src/components/Layout.jsx | 83 +++++++----------------------- frontend/src/pages/Login.jsx | 83 +++++++----------------------- 2 files changed, 40 insertions(+), 126 deletions(-) diff --git a/frontend/src/components/Layout.jsx b/frontend/src/components/Layout.jsx index 89bf5e4..e3c355c 100644 --- a/frontend/src/components/Layout.jsx +++ b/frontend/src/components/Layout.jsx @@ -236,7 +236,7 @@ const Layout = ({ children }) => { navigate("/hosts?action=add"); }; - // Generate low-poly triangular background pattern for dark mode + // Generate clean radial gradient background for dark mode - subtle and modern useEffect(() => { const generateBackground = () => { if ( @@ -252,7 +252,7 @@ const Layout = ({ children }) => { canvas.height = window.innerHeight; const ctx = canvas.getContext("2d"); - // Get theme colors + // Get theme colors - pick first color from each palette const xColors = themeConfig.login.xColors || [ "#667eea", "#764ba2", @@ -265,79 +265,36 @@ const Layout = ({ children }) => { "#f093fb", "#4facfe", ]; - const allColors = [...xColors, ...yColors]; - // Use date for daily variation + // Use date for daily color rotation const today = new Date(); const seed = today.getFullYear() * 10000 + today.getMonth() * 100 + today.getDate(); - - // Simple seeded random const random = (s) => { const x = Math.sin(s) * 10000; return x - Math.floor(x); }; - // Create a grid of points with some variance - const cellSize = themeConfig.login.cellSize || 150; // Larger cells for more subtle effect - const variance = themeConfig.login.variance || 0.5; - const cols = Math.ceil(canvas.width / cellSize) + 2; - const rows = Math.ceil(canvas.height / cellSize) + 2; + const color1 = xColors[Math.floor(random(seed) * xColors.length)]; + const color2 = yColors[Math.floor(random(seed + 1000) * yColors.length)]; - const points = []; - for (let y = -1; y < rows; y++) { - for (let x = -1; x < cols; x++) { - const idx = y * cols + x; - const offsetX = (random(seed + idx * 2) - 0.5) * cellSize * variance; - const offsetY = - (random(seed + idx * 2 + 1000) - 0.5) * cellSize * variance; - points.push({ - x: x * cellSize + offsetX, - y: y * cellSize + offsetY, - color: allColors[Math.floor(random(seed + idx) * allColors.length)], - }); - } - } + // Create clean radial gradient from center to bottom-right corner + const gradient = ctx.createRadialGradient( + canvas.width * 0.3, // Center slightly left + canvas.height * 0.3, // Center slightly up + 0, + canvas.width * 0.5, // Expand to cover screen + canvas.height * 0.5, + Math.max(canvas.width, canvas.height) * 1.2, + ); - // Draw triangles between points - for (let y = 0; y < rows - 1; y++) { - for (let x = 0; x < cols - 1; x++) { - const idx = y * cols + x; - const p1 = points[idx]; - const p2 = points[idx + 1]; - const p3 = points[idx + cols]; - const p4 = points[idx + cols + 1]; + // Subtle gradient with darker corners + gradient.addColorStop(0, color1); + gradient.addColorStop(0.6, color2); + gradient.addColorStop(1, "#0a0a0a"); // Very dark edges - // Draw two triangles per cell - // Triangle 1 - ctx.beginPath(); - ctx.moveTo(p1.x, p1.y); - ctx.lineTo(p2.x, p2.y); - ctx.lineTo(p3.x, p3.y); - ctx.closePath(); - - const gradient1 = ctx.createLinearGradient(p1.x, p1.y, p2.x, p2.y); - gradient1.addColorStop(0, p1.color); - gradient1.addColorStop(0.5, p2.color); - gradient1.addColorStop(1, p3.color); - ctx.fillStyle = gradient1; - ctx.fill(); - - // Triangle 2 - ctx.beginPath(); - ctx.moveTo(p2.x, p2.y); - ctx.lineTo(p4.x, p4.y); - ctx.lineTo(p3.x, p3.y); - ctx.closePath(); - - const gradient2 = ctx.createLinearGradient(p2.x, p2.y, p4.x, p4.y); - gradient2.addColorStop(0, p2.color); - gradient2.addColorStop(0.5, p4.color); - gradient2.addColorStop(1, p3.color); - ctx.fillStyle = gradient2; - ctx.fill(); - } - } + ctx.fillStyle = gradient; + ctx.fillRect(0, 0, canvas.width, canvas.height); }; generateBackground(); diff --git a/frontend/src/pages/Login.jsx b/frontend/src/pages/Login.jsx index e10ef67..cd638c6 100644 --- a/frontend/src/pages/Login.jsx +++ b/frontend/src/pages/Login.jsx @@ -56,7 +56,7 @@ const Login = () => { const navigate = useNavigate(); - // Generate low-poly triangular background pattern based on selected theme + // Generate clean radial gradient background - subtle and modern useEffect(() => { const generateBackground = () => { if (!canvasRef.current || !themeConfig?.login) return; @@ -66,7 +66,7 @@ const Login = () => { canvas.height = canvas.offsetHeight; const ctx = canvas.getContext("2d"); - // Get theme colors + // Get theme colors - pick first color from each palette const xColors = themeConfig.login.xColors || [ "#667eea", "#764ba2", @@ -79,79 +79,36 @@ const Login = () => { "#f093fb", "#4facfe", ]; - const allColors = [...xColors, ...yColors]; - // Use date for daily variation + // Use date for daily color rotation const today = new Date(); const seed = today.getFullYear() * 10000 + today.getMonth() * 100 + today.getDate(); - - // Simple seeded random const random = (s) => { const x = Math.sin(s) * 10000; return x - Math.floor(x); }; - // Create a grid of points with some variance - const cellSize = themeConfig.login.cellSize || 150; // Larger cells for more subtle effect - const variance = themeConfig.login.variance || 0.5; - const cols = Math.ceil(canvas.width / cellSize) + 2; - const rows = Math.ceil(canvas.height / cellSize) + 2; + const color1 = xColors[Math.floor(random(seed) * xColors.length)]; + const color2 = yColors[Math.floor(random(seed + 1000) * yColors.length)]; - const points = []; - for (let y = -1; y < rows; y++) { - for (let x = -1; x < cols; x++) { - const idx = y * cols + x; - const offsetX = (random(seed + idx * 2) - 0.5) * cellSize * variance; - const offsetY = - (random(seed + idx * 2 + 1000) - 0.5) * cellSize * variance; - points.push({ - x: x * cellSize + offsetX, - y: y * cellSize + offsetY, - color: allColors[Math.floor(random(seed + idx) * allColors.length)], - }); - } - } + // Create clean radial gradient from center to bottom-right corner + const gradient = ctx.createRadialGradient( + canvas.width * 0.3, // Center slightly left + canvas.height * 0.3, // Center slightly up + 0, + canvas.width * 0.5, // Expand to cover screen + canvas.height * 0.5, + Math.max(canvas.width, canvas.height) * 1.2, + ); - // Draw triangles between points - for (let y = 0; y < rows - 1; y++) { - for (let x = 0; x < cols - 1; x++) { - const idx = y * cols + x; - const p1 = points[idx]; - const p2 = points[idx + 1]; - const p3 = points[idx + cols]; - const p4 = points[idx + cols + 1]; + // Subtle gradient with darker corners + gradient.addColorStop(0, color1); + gradient.addColorStop(0.6, color2); + gradient.addColorStop(1, "#0a0a0a"); // Very dark edges - // Draw two triangles per cell - // Triangle 1 - ctx.beginPath(); - ctx.moveTo(p1.x, p1.y); - ctx.lineTo(p2.x, p2.y); - ctx.lineTo(p3.x, p3.y); - ctx.closePath(); - - const gradient1 = ctx.createLinearGradient(p1.x, p1.y, p2.x, p2.y); - gradient1.addColorStop(0, p1.color); - gradient1.addColorStop(0.5, p2.color); - gradient1.addColorStop(1, p3.color); - ctx.fillStyle = gradient1; - ctx.fill(); - - // Triangle 2 - ctx.beginPath(); - ctx.moveTo(p2.x, p2.y); - ctx.lineTo(p4.x, p4.y); - ctx.lineTo(p3.x, p3.y); - ctx.closePath(); - - const gradient2 = ctx.createLinearGradient(p2.x, p2.y, p4.x, p4.y); - gradient2.addColorStop(0, p2.color); - gradient2.addColorStop(0.5, p4.color); - gradient2.addColorStop(1, p3.color); - ctx.fillStyle = gradient2; - ctx.fill(); - } - } + ctx.fillStyle = gradient; + ctx.fillRect(0, 0, canvas.width, canvas.height); }; generateBackground(); From aba0f5cb6b75762d6adddc0e0af6030b70c7e74a Mon Sep 17 00:00:00 2001 From: Muhammad Ibrahim Date: Tue, 28 Oct 2025 18:53:46 +0000 Subject: [PATCH 11/12] feat: add subtle triangular accents to clean gradient background - Kept clean radial gradient base - Added very faint triangular shapes (2-5% opacity) - Sparse pattern - only 30% of grid positions - Random sizes (40-120px) and positions for organic feel - Perfect balance of clean + subtle geometric detail --- frontend/src/components/Layout.jsx | 31 +++++++++++++++++++++++++++++- frontend/src/pages/Login.jsx | 31 +++++++++++++++++++++++++++++- 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/Layout.jsx b/frontend/src/components/Layout.jsx index e3c355c..966b254 100644 --- a/frontend/src/components/Layout.jsx +++ b/frontend/src/components/Layout.jsx @@ -236,7 +236,7 @@ const Layout = ({ children }) => { navigate("/hosts?action=add"); }; - // Generate clean radial gradient background for dark mode - subtle and modern + // Generate clean radial gradient background with subtle triangular accents for dark mode useEffect(() => { const generateBackground = () => { if ( @@ -295,6 +295,35 @@ const Layout = ({ children }) => { ctx.fillStyle = gradient; ctx.fillRect(0, 0, canvas.width, canvas.height); + + // Add subtle triangular shapes as accents + const cellSize = 200; + const cols = Math.ceil(canvas.width / cellSize) + 1; + const rows = Math.ceil(canvas.height / cellSize) + 1; + + for (let y = 0; y < rows; y++) { + for (let x = 0; x < cols; x++) { + const idx = y * cols + x; + // Only draw some triangles (sparse pattern) + if (random(seed + idx + 5000) > 0.7) { + const baseX = + x * cellSize + random(seed + idx * 3) * cellSize * 0.8; + const baseY = + y * cellSize + random(seed + idx * 3 + 100) * cellSize * 0.8; + const size = 40 + random(seed + idx * 4) * 80; + + ctx.beginPath(); + ctx.moveTo(baseX, baseY); + ctx.lineTo(baseX + size, baseY); + ctx.lineTo(baseX + size / 2, baseY - size * 0.866); + ctx.closePath(); + + // Very faint white with low opacity + ctx.fillStyle = `rgba(255, 255, 255, ${0.02 + random(seed + idx * 5) * 0.03})`; + ctx.fill(); + } + } + } }; generateBackground(); diff --git a/frontend/src/pages/Login.jsx b/frontend/src/pages/Login.jsx index cd638c6..c0e38ac 100644 --- a/frontend/src/pages/Login.jsx +++ b/frontend/src/pages/Login.jsx @@ -56,7 +56,7 @@ const Login = () => { const navigate = useNavigate(); - // Generate clean radial gradient background - subtle and modern + // Generate clean radial gradient background with subtle triangular accents useEffect(() => { const generateBackground = () => { if (!canvasRef.current || !themeConfig?.login) return; @@ -109,6 +109,35 @@ const Login = () => { ctx.fillStyle = gradient; ctx.fillRect(0, 0, canvas.width, canvas.height); + + // Add subtle triangular shapes as accents + const cellSize = 200; + const cols = Math.ceil(canvas.width / cellSize) + 1; + const rows = Math.ceil(canvas.height / cellSize) + 1; + + for (let y = 0; y < rows; y++) { + for (let x = 0; x < cols; x++) { + const idx = y * cols + x; + // Only draw some triangles (sparse pattern) + if (random(seed + idx + 5000) > 0.7) { + const baseX = + x * cellSize + random(seed + idx * 3) * cellSize * 0.8; + const baseY = + y * cellSize + random(seed + idx * 3 + 100) * cellSize * 0.8; + const size = 40 + random(seed + idx * 4) * 80; + + ctx.beginPath(); + ctx.moveTo(baseX, baseY); + ctx.lineTo(baseX + size, baseY); + ctx.lineTo(baseX + size / 2, baseY - size * 0.866); + ctx.closePath(); + + // Very faint white with low opacity + ctx.fillStyle = `rgba(255, 255, 255, ${0.02 + random(seed + idx * 5) * 0.03})`; + ctx.fill(); + } + } + } }; generateBackground(); From f6d23e45b2afeb9b0413fa8b52c1d9794f92c988 Mon Sep 17 00:00:00 2001 From: Muhammad Ibrahim Date: Tue, 28 Oct 2025 18:55:30 +0000 Subject: [PATCH 12/12] feat: make triangular accents more visible across background - Increased triangle opacity from 2-5% to 5-13% - Increased coverage from 30% to 60% of grid positions - Slightly larger triangles (50-150px vs 40-120px) - Smaller cell size (180px vs 200px) for better distribution - Now clearly visible across entire background --- frontend/src/components/Layout.jsx | 14 +++++++------- frontend/src/pages/Login.jsx | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/frontend/src/components/Layout.jsx b/frontend/src/components/Layout.jsx index 966b254..f647487 100644 --- a/frontend/src/components/Layout.jsx +++ b/frontend/src/components/Layout.jsx @@ -296,21 +296,21 @@ const Layout = ({ children }) => { ctx.fillStyle = gradient; ctx.fillRect(0, 0, canvas.width, canvas.height); - // Add subtle triangular shapes as accents - const cellSize = 200; + // Add subtle triangular shapes as accents across entire background + const cellSize = 180; const cols = Math.ceil(canvas.width / cellSize) + 1; const rows = Math.ceil(canvas.height / cellSize) + 1; for (let y = 0; y < rows; y++) { for (let x = 0; x < cols; x++) { const idx = y * cols + x; - // Only draw some triangles (sparse pattern) - if (random(seed + idx + 5000) > 0.7) { + // Draw more triangles (less sparse) + if (random(seed + idx + 5000) > 0.4) { const baseX = x * cellSize + random(seed + idx * 3) * cellSize * 0.8; const baseY = y * cellSize + random(seed + idx * 3 + 100) * cellSize * 0.8; - const size = 40 + random(seed + idx * 4) * 80; + const size = 50 + random(seed + idx * 4) * 100; ctx.beginPath(); ctx.moveTo(baseX, baseY); @@ -318,8 +318,8 @@ const Layout = ({ children }) => { ctx.lineTo(baseX + size / 2, baseY - size * 0.866); ctx.closePath(); - // Very faint white with low opacity - ctx.fillStyle = `rgba(255, 255, 255, ${0.02 + random(seed + idx * 5) * 0.03})`; + // More visible white with slightly higher opacity + ctx.fillStyle = `rgba(255, 255, 255, ${0.05 + random(seed + idx * 5) * 0.08})`; ctx.fill(); } } diff --git a/frontend/src/pages/Login.jsx b/frontend/src/pages/Login.jsx index c0e38ac..4141c48 100644 --- a/frontend/src/pages/Login.jsx +++ b/frontend/src/pages/Login.jsx @@ -110,21 +110,21 @@ const Login = () => { ctx.fillStyle = gradient; ctx.fillRect(0, 0, canvas.width, canvas.height); - // Add subtle triangular shapes as accents - const cellSize = 200; + // Add subtle triangular shapes as accents across entire background + const cellSize = 180; const cols = Math.ceil(canvas.width / cellSize) + 1; const rows = Math.ceil(canvas.height / cellSize) + 1; for (let y = 0; y < rows; y++) { for (let x = 0; x < cols; x++) { const idx = y * cols + x; - // Only draw some triangles (sparse pattern) - if (random(seed + idx + 5000) > 0.7) { + // Draw more triangles (less sparse) + if (random(seed + idx + 5000) > 0.4) { const baseX = x * cellSize + random(seed + idx * 3) * cellSize * 0.8; const baseY = y * cellSize + random(seed + idx * 3 + 100) * cellSize * 0.8; - const size = 40 + random(seed + idx * 4) * 80; + const size = 50 + random(seed + idx * 4) * 100; ctx.beginPath(); ctx.moveTo(baseX, baseY); @@ -132,8 +132,8 @@ const Login = () => { ctx.lineTo(baseX + size / 2, baseY - size * 0.866); ctx.closePath(); - // Very faint white with low opacity - ctx.fillStyle = `rgba(255, 255, 255, ${0.02 + random(seed + idx * 5) * 0.03})`; + // More visible white with slightly higher opacity + ctx.fillStyle = `rgba(255, 255, 255, ${0.05 + random(seed + idx * 5) * 0.08})`; ctx.fill(); } }