From 0660d1300649f831610a29b7f84ab6e5739a5889 Mon Sep 17 00:00:00 2001 From: salmad3 Date: Thu, 13 Nov 2025 03:10:43 -0500 Subject: [PATCH 01/12] add atomone theme and color theme system --- .../components/ui/switch/ColorModeSwitch.vue | 70 +++++++----- .../src/components/users/UserAvatar.vue | 8 +- .../src/composables/useColorTheme.ts | 104 ++++++++++++++++++ packages/frontend-main/src/main.ts | 10 ++ packages/frontend-main/src/style.css | 62 ++++++++++- .../frontend-main/src/views/EnvConfigView.vue | 9 +- 6 files changed, 230 insertions(+), 33 deletions(-) create mode 100644 packages/frontend-main/src/composables/useColorTheme.ts diff --git a/packages/frontend-main/src/components/ui/switch/ColorModeSwitch.vue b/packages/frontend-main/src/components/ui/switch/ColorModeSwitch.vue index 37c9d613..6a39874e 100644 --- a/packages/frontend-main/src/components/ui/switch/ColorModeSwitch.vue +++ b/packages/frontend-main/src/components/ui/switch/ColorModeSwitch.vue @@ -1,38 +1,56 @@ diff --git a/packages/frontend-main/src/components/users/UserAvatar.vue b/packages/frontend-main/src/components/users/UserAvatar.vue index 352fc942..4af30c88 100644 --- a/packages/frontend-main/src/components/users/UserAvatar.vue +++ b/packages/frontend-main/src/components/users/UserAvatar.vue @@ -1,8 +1,9 @@ diff --git a/packages/frontend-main/src/composables/useColorTheme.ts b/packages/frontend-main/src/composables/useColorTheme.ts new file mode 100644 index 00000000..bce721de --- /dev/null +++ b/packages/frontend-main/src/composables/useColorTheme.ts @@ -0,0 +1,104 @@ +import { useStorage } from '@vueuse/core'; +import { watch } from 'vue'; + +export type ColorTheme = 'light' | 'dark' | 'atomone'; + +const THEME_STORAGE_KEY = 'dither-color-theme'; + +let themeInstance: ReturnType | null = null; + +function createThemeInstance() { + const theme = useStorage(THEME_STORAGE_KEY, 'light'); + const html = document.documentElement; + + function applyTheme(newTheme: ColorTheme) { + html.classList.remove('light', 'dark', 'atomone'); + html.classList.add(newTheme); + theme.value = newTheme; + + if (typeof window !== 'undefined') { + const metaThemeColor = document.querySelector('meta[name="theme-color"]'); + if (metaThemeColor) { + if (newTheme === 'dark' || newTheme === 'atomone') { + metaThemeColor.setAttribute('content', '#1a1a1a'); + } else { + metaThemeColor.setAttribute('content', '#ffffff'); + } + } + } + } + + function cycleTheme() { + const themes: ColorTheme[] = ['light', 'dark', 'atomone']; + const currentIndex = themes.indexOf(theme.value); + const nextIndex = (currentIndex + 1) % themes.length; + applyTheme(themes[nextIndex]); + } + + if (typeof window !== 'undefined') { + applyTheme(theme.value); + + const observer = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + if (mutation.type === 'attributes' && mutation.attributeName === 'class') { + const currentClass = html.className; + const hasTheme = currentClass.includes('light') || currentClass.includes('dark') || currentClass.includes('atomone'); + + if (!hasTheme) { + requestAnimationFrame(() => { + applyTheme(theme.value); + }); + } else { + const currentTheme = theme.value; + if (!currentClass.includes(currentTheme)) { + requestAnimationFrame(() => { + applyTheme(currentTheme); + }); + } + } + } + }); + }); + + observer.observe(html, { + attributes: true, + attributeFilter: ['class'], + }); + + if (window.location) { + const originalPushState = history.pushState; + const originalReplaceState = history.replaceState; + + history.pushState = function (...args) { + originalPushState.apply(history, args); + requestAnimationFrame(() => applyTheme(theme.value)); + }; + + history.replaceState = function (...args) { + originalReplaceState.apply(history, args); + requestAnimationFrame(() => applyTheme(theme.value)); + }; + + window.addEventListener('popstate', () => { + requestAnimationFrame(() => applyTheme(theme.value)); + }); + } + } + + watch(theme, (newTheme) => { + applyTheme(newTheme); + }, { immediate: true }); + + return { + theme, + cycleTheme, + setTheme: applyTheme, + }; +} + +export function useColorTheme() { + if (!themeInstance) { + themeInstance = createThemeInstance(); + } + return themeInstance; +} diff --git a/packages/frontend-main/src/main.ts b/packages/frontend-main/src/main.ts index d9ae0e9a..724ce52d 100644 --- a/packages/frontend-main/src/main.ts +++ b/packages/frontend-main/src/main.ts @@ -4,6 +4,7 @@ import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'; import { createApp } from 'vue'; import { createI18n } from 'vue-i18n'; +import { useColorTheme } from '@/composables/useColorTheme'; import { messages } from '@/localization'; import App from './App.vue'; @@ -12,6 +13,15 @@ import router from './router.ts'; import './style.css'; +const { theme } = useColorTheme(); + +router.afterEach(() => { + const html = document.documentElement; + const currentTheme = theme.value; + html.classList.remove('light', 'dark', 'atomone'); + html.classList.add(currentTheme); +}); + const pinia = createPinia(); pinia.use(piniaPluginPersistedstate); const app = createApp(App); diff --git a/packages/frontend-main/src/style.css b/packages/frontend-main/src/style.css index 3c13da38..90c0031d 100644 --- a/packages/frontend-main/src/style.css +++ b/packages/frontend-main/src/style.css @@ -1,4 +1,5 @@ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&display=swap'); @import 'tailwindcss'; @import 'tw-animate-css'; @@ -93,6 +94,9 @@ --text-base--line-height: normal; --text-sm--line-height: normal; --text-xs--line-height: normal; + + --atomone-accent: oklch(0.55 0.15 250); + --atomone-accent-muted: oklch(0.65 0.08 250); } .dark { @@ -130,6 +134,50 @@ --sidebar-ring: oklch(0.439 0 0); } +.atomone { + --background: oklch(0.05 0 0); + --foreground: oklch(1 0 0); + --card: oklch(0.08 0 0); + --card-foreground: oklch(1 0 0); + --popover: oklch(0.08 0 0); + --popover-foreground: oklch(1 0 0); + --primary: oklch(1 0 0); + --primary-foreground: oklch(0.05 0 0); + --secondary: oklch(0.15 0 0); + --secondary-foreground: oklch(1 0 0); + --muted: oklch(0.15 0 0); + --muted-foreground: oklch(0.62 0 0); + --accent: oklch(0.75 0.15 200); + --accent-foreground: oklch(0.05 0 0); + --destructive: oklch(0.396 0.141 25.723); + --destructive-foreground: oklch(0.637 0.237 25.331); + --border: oklch(0.18 0 0); + --input: oklch(0.15 0 0); + --ring: oklch(0.75 0.15 200); + --chart-1: oklch(0.75 0.15 200); + --chart-2: oklch(0.7 0.18 300); + --chart-3: oklch(0.769 0.188 70.08); + --chart-4: oklch(0.627 0.265 303.9); + --chart-5: oklch(0.645 0.246 16.439); + --sidebar: oklch(0.08 0 0); + --sidebar-foreground: oklch(1 0 0); + --sidebar-primary: oklch(1 0 0); + --sidebar-primary-foreground: oklch(0.05 0 0); + --sidebar-accent: oklch(0.15 0 0); + --sidebar-accent-foreground: oklch(1 0 0); + --sidebar-border: oklch(0.18 0 0); + --sidebar-ring: oklch(0.75 0.15 200); +} + +.atomone, +.atomone body { + background-image: + linear-gradient(rgba(255, 255, 255, 0.02) 1px, transparent 1px), + linear-gradient(90deg, rgba(255, 255, 255, 0.02) 1px, transparent 1px); + background-size: 24px 24px; + background-attachment: fixed; +} + @layer base { * { @apply border-border outline-ring/50; @@ -137,8 +185,20 @@ body { @apply bg-background text-foreground; overflow-y: scroll; - font-family: 'Inter', sans-serif; + font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; line-height: normal; + font-feature-settings: 'kern' 1, 'liga' 1; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + } + + h1, h2, h3, h4, h5, h6, + [class*="font-display"], + .logo, + .brand-text { + font-family: 'Space Grotesk', 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; + font-feature-settings: 'kern' 1, 'liga' 1; + letter-spacing: -0.01em; } button:not([disabled]), [role='button']:not([disabled]) { diff --git a/packages/frontend-main/src/views/EnvConfigView.vue b/packages/frontend-main/src/views/EnvConfigView.vue index b56fb31d..84fd1743 100644 --- a/packages/frontend-main/src/views/EnvConfigView.vue +++ b/packages/frontend-main/src/views/EnvConfigView.vue @@ -1,10 +1,12 @@ From f45daf1f60e793d499f9d0776f789feec0aead78 Mon Sep 17 00:00:00 2001 From: salmad3 Date: Thu, 13 Nov 2025 03:10:55 -0500 Subject: [PATCH 03/12] move wallet connect to top right and make it navigate to profile --- .../WalletConnectButton.vue | 28 ++++++++++++------ .../src/layouts/panels/RightPanel.vue | 29 +++++++++++++++++-- 2 files changed, 45 insertions(+), 12 deletions(-) diff --git a/packages/frontend-main/src/components/wallet/WalletConnectButton/WalletConnectButton.vue b/packages/frontend-main/src/components/wallet/WalletConnectButton/WalletConnectButton.vue index 99657e7d..6fcb49aa 100644 --- a/packages/frontend-main/src/components/wallet/WalletConnectButton/WalletConnectButton.vue +++ b/packages/frontend-main/src/components/wallet/WalletConnectButton/WalletConnectButton.vue @@ -1,5 +1,7 @@