diff --git a/.gitignore b/.gitignore index 4f498b0..592b6c2 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ node_modules data .DS_Store .env +.vscode \ No newline at end of file diff --git a/public/app.js b/public/app.js index 239964d..383e293 100644 --- a/public/app.js +++ b/public/app.js @@ -4,6 +4,10 @@ const canvas = document.getElementById('network'); const ctx = canvas.getContext('2d'); let particles = []; +function getThemeColor(varName) { + return getComputedStyle(document.documentElement).getPropertyValue(varName).trim(); +} + function resize() { canvas.width = window.innerWidth; canvas.height = window.innerHeight; @@ -32,7 +36,7 @@ class Particle { draw() { ctx.beginPath(); ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2); - ctx.fillStyle = '#4ade80'; + ctx.fillStyle = getThemeColor('--color-particle'); ctx.fill(); } } @@ -54,7 +58,7 @@ const updateParticles = (count) => { const animate = () => { ctx.clearRect(0, 0, canvas.width, canvas.height); - ctx.strokeStyle = 'rgba(74, 222, 128, 0.15)'; + ctx.strokeStyle = getThemeColor('--color-particle-link'); ctx.lineWidth = 1; for (let i = 0; i < particles.length; i++) { for (let j = i + 1; j < particles.length; j++) { @@ -154,3 +158,37 @@ countEl.classList.add('loaded'); updateParticles(initialCount); animate(); +const themes = [ + 'default.css', + 'tokyo-night.css' +]; + +let currentThemeIndex = 0; + +function loadSavedTheme() { + const savedTheme = localStorage.getItem('hypermind-theme'); + if (savedTheme) { + const themeLink = document.getElementById('theme-css'); + themeLink.href = `/themes/${savedTheme}`; + currentThemeIndex = themes.indexOf(savedTheme); + if (currentThemeIndex === -1) currentThemeIndex = 0; + } else { + const themeLink = document.getElementById('theme-css'); + const currentTheme = themeLink.href.split('/').pop(); + currentThemeIndex = themes.indexOf(currentTheme); + if (currentThemeIndex === -1) currentThemeIndex = 0; + } +} + +function cycleTheme() { + currentThemeIndex = (currentThemeIndex + 1) % themes.length; + const newTheme = themes[currentThemeIndex]; + const themeLink = document.getElementById('theme-css'); + themeLink.href = `/themes/${newTheme}`; + localStorage.setItem('hypermind-theme', newTheme); +} + +document.getElementById('theme-switcher').addEventListener('click', cycleTheme); + +loadSavedTheme(); + diff --git a/public/index.html b/public/index.html index 77d90c0..402d51c 100644 --- a/public/index.html +++ b/public/index.html @@ -4,6 +4,7 @@ Hypermind + @@ -12,7 +13,7 @@
{{COUNT}}
Active Nodes
ID: {{ID}}
@@ -65,6 +66,9 @@
+ diff --git a/public/style.css b/public/style.css index cd016e1..2772318 100644 --- a/public/style.css +++ b/public/style.css @@ -6,29 +6,37 @@ body { justify-content: center; align-items: center; height: 100vh; - background: #111; - color: #eee; + background: var(--color-bg-main); + color: var(--color-text-default); margin: 0; } .container { text-align: center; position: relative; z-index: 10; } #network { position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: 1; } -.count { font-size: 8rem; font-weight: bold; color: #4ade80; transition: color 0.2s; visibility: hidden; } +.count { font-size: 8rem; font-weight: bold; color: var(--color-count); transition: color 0.2s; visibility: hidden; } .count.loaded { visibility: visible; } -.label { font-size: 1.5rem; color: #9ca3af; margin-top: 1rem; } -.footer { margin-top: 2rem; font-size: 0.9rem; color: #4b5563; } -.debug { font-size: 0.8rem; color: #4b5563; margin-top: 1rem; } -.debug-link { color: #4b5563; border-bottom: 1px dotted #4b5563; cursor: pointer; } -.debug-link:hover { color: #9ca3af; } -a { color: #4b5563; text-decoration: none; border-bottom: 1px dotted #4b5563; } - +.label { font-size: 1.5rem; color: var(--color-text-main-label); margin-top: 1rem; } +.footer { margin-top: 2rem; font-size: 0.9rem; color: var(--color-text-footer); } +.debug { font-size: 0.8rem; color: var(--color-text-debug); margin-top: 1rem; } +.debug-link { color: var(--color-text-debug-link); border-bottom: 1px dotted; cursor: pointer; transition: color 0.2s; } +.debug-link:hover { color: var(--color-text-debug-link-hover); } +a { color: var(--color-text-secondary); text-decoration: none; border-bottom: 1px dotted var(--color-text-secondary); } .pulse { animation: pulse 0.5s ease-in-out; } @keyframes pulse { 0% { transform: scale(1); } - 50% { transform: scale(1.1); color: #fff; } + 50% { transform: scale(1.1); color: var(--color-pulse); } 100% { transform: scale(1); } } - +.footer a { + color: var(--color-text-footer); + border-bottom: 1px dotted var(--color-text-footer); + cursor: pointer; + transition: color 0.2s, border-color 0.2s; +} +.footer a:hover { + color: var(--color-text-footer-hover); + border-color: var(--color-text-footer-hover); +} .modal { display: none; position: fixed; @@ -37,12 +45,12 @@ a { color: #4b5563; text-decoration: none; border-bottom: 1px dotted #4b5563; } top: 0; width: 100%; height: 100%; - background: rgba(0, 0, 0, 0.8); + background: var(--color-bg-overlay); } .modal.active { display: flex; align-items: center; justify-content: center; } .modal-content { - background: #111; - border: 1px solid #222; + background: var(--color-modal-bg); + border: 1px solid var(--color-modal-border); padding: 2rem; max-width: 500px; width: 90%; @@ -50,7 +58,7 @@ a { color: #4b5563; text-decoration: none; border-bottom: 1px dotted #4b5563; } } .modal-title { font-size: 0.9rem; - color: #666; + color: var(--color-modal-title); margin-bottom: 1.5rem; text-transform: uppercase; letter-spacing: 1px; @@ -61,27 +69,53 @@ a { color: #4b5563; text-decoration: none; border-bottom: 1px dotted #4b5563; } right: 1.5rem; background: none; border: none; - color: #333; + color: var(--color-modal-close-btn); font-size: 1.2rem; cursor: pointer; + transition: color 0.2s; } -.close-btn:hover { color: #666; } +.close-btn:hover { color: var(--color-modal-close-btn-hover); } .stat-row { display: flex; justify-content: space-between; padding: 0.5rem 0; - border-bottom: 1px solid #1a1a1a; + border-bottom: 1px solid var(--color-modal-stat-div); font-size: 0.85rem; } .stat-row:last-child { border-bottom: none; } -.stat-label { color: #4b5563; } +.stat-label { color: var(--color-modal-stat-label); } .stat-value { - color: #9ca3af; + color: var(--color-modal-stat-value;); font-variant-numeric: tabular-nums; } .update-time { text-align: center; font-size: 0.7rem; - color: #333; + color: var(--color-modal-footer); margin-top: 1rem; } +.theme-btn { + position: fixed; + bottom: 1.5rem; + left: 1.5rem; + cursor: pointer; + z-index: 100; + display: flex; + align-items: center; + justify-content: center; + background: none; + border: none; + padding: 0; + transition: all 0.2s ease; +} + +.theme-btn .material-symbols-outlined { + color: var(--color-theme-toggle); + font-size: 28px; + font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24; + transition: color 0.2s ease; +} + +.theme-btn:hover .material-symbols-outlined { + color: var(--color-theme-toggle-hover); +} diff --git a/public/themes/default.css b/public/themes/default.css new file mode 100644 index 0000000..0e832ba --- /dev/null +++ b/public/themes/default.css @@ -0,0 +1,31 @@ +:root { + --color-count: #4ade80; + --color-particle: #4ade80; + --color-particle-link: rgba(74, 222, 128, 0.15); + --color-pulse: #fff; + --color-theme-toggle: #4ade80; + --color-theme-toggle-hover: #fff; + + --color-text-default: #eee; + --color-text-secondary: #4b5563; + --color-text-main-label: #9ca3af; + --color-text-footer: #4b5563; + --color-text-footer-hover: #9ca3af; + --color-text-debug: #4b5563; + --color-text-debug-link: #4b5563; + --color-text-debug-link-hover: #9ca3af; + + --color-bg-overlay: rgba(0, 0, 0, 0.8); + --color-bg-main: #111; + + --color-modal-bg: #111; + --color-modal-border: #222; + --color-modal-title: #666; + --color-modal-close-btn: #333; + --color-modal-close-btn-hover: #666; + --color-modal-stat-div: #1a1a1a; + --color-modal-stat-label: #4b5563; + --color-modal-stat-value: #9ca3af; + --color-modal-footer: #333; + +} \ No newline at end of file diff --git a/public/themes/tokyo-night.css b/public/themes/tokyo-night.css new file mode 100644 index 0000000..7eaaa29 --- /dev/null +++ b/public/themes/tokyo-night.css @@ -0,0 +1,24 @@ +:root { + --color-primary: #ff6b6b; + --color-particle-link: rgba(255, 107, 107, 0.15); + --color-pulse: #fff; + --color-theme-toggle: #ff6b6b; + --color-theme-toggle-hover: #fff; + + + --color-text-default: #eee; + --color-text-primary: #f7d794; + --color-text-secondary: #ff92a1; + --color-text-tertiary: #ff92a1; + + --color-bg-main: #00172e; + --color-bg-overlay: rgba(0, 1, 2, 0.8); + + --color-border-light: #222; + + --color-modal-close-btn: #ff92a1; + --color-modal-close-btn-hover: #f7d794; + --color-modal-stats-border: #ff92a1; + --color-modal-footer: #2d3436; + +} \ No newline at end of file