diff --git a/public/default-avatar.png b/src/assets/default-avatar.png similarity index 100% rename from public/default-avatar.png rename to src/assets/default-avatar.png diff --git a/src/assets/logo.png b/src/assets/logo.png new file mode 100644 index 0000000..2e3b818 Binary files /dev/null and b/src/assets/logo.png differ diff --git a/src/components/AppIcon.ts b/src/components/AppIcon.ts index ee32067..72438d5 100644 --- a/src/components/AppIcon.ts +++ b/src/components/AppIcon.ts @@ -2,6 +2,7 @@ import { LitElement, css, html } from 'lit'; import { customElement, property } from 'lit/decorators.js'; import '@components/EveLink'; +import { ifDefined } from 'lit/directives/if-defined.js'; import { when } from 'lit/directives/when.js'; @customElement('arx-app-icon') @@ -18,6 +19,12 @@ export class AppIcon extends LitElement { @property() name = 'App'; + @property({ type: Boolean }) + small = false; + + @property({ type: Boolean }) + selected = false; + private iconElement?: HTMLElement; static override styles = [ @@ -100,6 +107,12 @@ export class AppIcon extends LitElement { } } + :host([small]) .icon { + width: 64px; + height: 64px; + border-radius: 12px; + } + .icon-svg { color: white; filter: drop-shadow( @@ -130,6 +143,15 @@ export class AppIcon extends LitElement { opacity: 0.85; } + :host([selected]) .app-name { + color: var(--color-primary-content); + } + + :host([small]) .app-name { + font-size: 14px; + margin-top: 2px; + } + .app-icon:hover .icon { transform: scale(1.05) translateZ(10px) rotateX(var(--rotate-x, 0deg)) rotateY(var(--rotate-y, 0deg)); box-shadow: @@ -137,6 +159,10 @@ export class AppIcon extends LitElement { 0 0 0 1px rgba(255, 255, 255, 0.15); } + :host([small]) .app-icon:hover .icon { + transform: scale(1.05) translateZ(5px) rotateX(var(--rotate-x, 0deg)) rotateY(var(--rotate-y, 0deg)); + } + .app-icon:hover .app-name { opacity: 1; transform: translateY(2px) scale(1.02); @@ -146,6 +172,10 @@ export class AppIcon extends LitElement { transform: scale(1.08) translateZ(15px); } + :host([small]) .app-icon:hover .icon-svg { + transform: scale(1.08) translateZ(8px); + } + .app-icon:active .icon { transform: scale(var(--tap-scale)) translateZ(0); box-shadow: 0 2px 5px rgba(0, 0, 0, calc(var(--shadow-opacity) * 0.8)); @@ -222,10 +252,10 @@ export class AppIcon extends LitElement { this.icon, () => html``, )} diff --git a/src/components/Header.ts b/src/components/Header.ts index e8745c9..2ef9272 100644 --- a/src/components/Header.ts +++ b/src/components/Header.ts @@ -35,10 +35,6 @@ export class Header extends LitElement { header { background: var(--color-primary); backdrop-filter: blur(10px); - border: var(--border) solid var(--color-primary-content); - box-shadow: calc(var(--depth) * 4px) calc(var(--depth) * 4px) - calc(var(--depth) * 8px) - oklch(from var(--color-base-content) l c h / 0.2); height: var(--font-2xl, 3rem); font-size: var(--font-md, 1rem); transition: all 0.3s ease; diff --git a/src/components/NostrAvatar.ts b/src/components/NostrAvatar.ts index fbd43d3..caad32d 100644 --- a/src/components/NostrAvatar.ts +++ b/src/components/NostrAvatar.ts @@ -1,8 +1,8 @@ +import defaultAvatar from '@assets/default-avatar.png'; import type { NDKUserProfile } from '@nostr-dev-kit/ndk'; import { LitElement, css, html } from 'lit-element'; import { customElement, property } from 'lit/decorators.js'; type AvatarSize = 'short' | 'medium' | 'large' | 'huge'; -import defaultAvatar from '@/default-avatar.png'; @customElement('arx-nostr-avatar') export class ArxNostrAvatar extends LitElement { diff --git a/src/components/Sidebar.ts b/src/components/Sidebar.ts new file mode 100644 index 0000000..253f7a1 --- /dev/null +++ b/src/components/Sidebar.ts @@ -0,0 +1,267 @@ +import defaultAvatar from '@assets/default-avatar.png'; +import logo from '@assets/logo.png'; +import type { NDKUserProfile } from '@nostr-dev-kit/ndk'; +import { LitElement, css, html } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; +import { map } from 'lit/directives/map.js'; + +@customElement('arx-sidebar') +export default class Sidebar extends LitElement { + @property({ type: String }) + currentPath = ''; + + @property({ type: String }) + userNpub = ''; + + @property({ type: Object }) + userProfile: NDKUserProfile | undefined = undefined; + + @property({ type: Array }) + apps = [ + { + id: 1, + href: 'beacon', + name: 'Beacon', + color: '#FF8C00', + icon: 'fa-solid:sun', + }, + { + id: 2, + href: 'arbor', + name: 'Arbor', + color: '#FF4040', + icon: 'fa-solid:tree', + }, + { + id: 3, + href: 'wallet', + name: 'Wallet', + color: '#1E90FF', + icon: 'fa-solid:spa', + }, + { + id: 4, + href: 'settings', + name: 'Settings', + color: '#7B68EE', + icon: 'fa-solid:tools', + }, + ]; + + static override styles = css` + :host { + overflow-x: hidden; + background: var(--color-base-200); + padding: 0 0 1.5rem 0; + display: flex; + flex-direction: column; + align-items: center; + overflow-y: overlay; + scrollbar-width: none; + position: relative; + height: 100%; + } + + ::-webkit-scrollbar { + width: 4px; + height: 4px; + background: transparent; + } + + ::-webkit-scrollbar-track { + background: transparent; + margin: 3px; + } + + ::-webkit-scrollbar-thumb { + background: rgba(100, 100, 100, 0.4); + border-radius: 10px; + transition: all 0.3s ease; + } + + ::-webkit-scrollbar-thumb:hover { + background: rgba(100, 100, 100, 0.7); + } + + :host:hover { + scrollbar-color: rgba(100, 100, 100, 0.4) transparent; + } + + :host::after { + content: ''; + position: absolute; + right: 0; + top: 0; + width: 10px; + height: 100%; + opacity: 0; + pointer-events: none; + background: linear-gradient(to right, transparent, rgba(0, 0, 0, 0.03)); + transition: opacity 0.3s ease; + } + + :host:hover::after { + opacity: 1; + } + + .app-icon-small { + margin-bottom: 1.5rem; + width: 80px; + } + + .logo { + height: 64px; + margin-bottom: 1.5rem; + cursor: pointer; + transition: transform 0.2s ease, filter 0.3s ease; + border-radius: var(--radius-field); + padding: 8px; + background: none; + filter: + drop-shadow(0 0 2px rgba(0, 0, 0, 1)) + drop-shadow(0 0 1px rgba(255, 255, 255, 1)); + } + + .logo-container { + width: 100%; + background: var(--color-accent); + display: flex; + justify-content: center; + padding: 12px 0; + margin-bottom: 1.5rem; + position: relative; + } + + .logo-container::after { + content: ''; + position: absolute; + bottom: -5px; + left: 0; + right: 0; + height: 6px; + background: rgba(0, 0, 0, 0.08); + border-radius: 50%; + filter: blur(3px); + } + + .logo-container .logo { + margin-bottom: 0; + transform: translateY(0); + } + + .logo:hover { + transform: translateY(-2px) scale(1.02); + filter: + drop-shadow(0 0 3px rgba(0, 0, 0, 0.8)) + drop-shadow(0 0 2px rgba(255, 255, 255, 0.6)); + } + + .logo:active { + transform: translateY(1px) scale(0.98); + filter: + drop-shadow(0 0 1px rgba(0, 0, 0, 0.6)); + transition-duration: 0.1s; + } + + .app-icon-small.active { + transform: scale(1.15); + position: relative; + z-index: 10; + } + + .app-icon-small.active::before { + content: ''; + position: absolute; + top: -8px; + left: -8px; + right: -8px; + bottom: -8px; + background: var(--color-accent); + z-index: -1; + } + + .sidebar-item { + position: relative; + margin-bottom: 1.5rem; + transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); + } + + .sidebar-item:hover { + transform: translateY(-2px); + } + + .profile-avatar { + width: 64px; + height: 64px; + border-radius: 50%; + object-fit: cover; + transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); + border: 2px solid var(--color-base-300); + } + + .profile-avatar.active { + border-color: var(--color-primary); + transform: scale(1.15); + box-shadow: 0 3px 10px rgba(0, 0, 0, 0.15); + } + + .profile-item { + margin-bottom: 1.5rem; + transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); + display: flex; + justify-content: center; + } + + .profile-item:hover .profile-avatar { + transform: translateY(-2px) scale(1.05); + } + `; + + private handleLogoClick() { + this.dispatchEvent(new CustomEvent('navigate', { detail: 'home' })); + } + + override render() { + const activePath = this.currentPath.split('/')[0]; + + return html` + + ${map( + this.apps, + (app) => html` + + `, + )} +
+
+ + Profile { + (e.target as HTMLImageElement).src = defaultAvatar; + }} + /> + +
+ `; + } +} diff --git a/src/default-avatar.png b/src/default-avatar.png deleted file mode 100644 index 02e81ef..0000000 Binary files a/src/default-avatar.png and /dev/null differ diff --git a/src/electron/main.ts b/src/electron/main.ts index c2eb3d8..14a0d67 100644 --- a/src/electron/main.ts +++ b/src/electron/main.ts @@ -38,9 +38,11 @@ ipcMain.handle('relay:status', () => { function createWindow(): void { const mainWindow = new BrowserWindow({ - width: 1024, + width: 1366, height: 768, show: false, + minWidth: 1366, + minHeight: 768, autoHideMenuBar: true, webPreferences: { preload: path.join(__dirname, '../preload/preload.mjs'), diff --git a/src/main.ts b/src/main.ts index 45d1d50..af68c9c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,13 +1,12 @@ -import './style.css'; -import '@components/ErrorView'; -import '@components/NostrAvatar'; -import '@components/LoadingView'; -import '@components/NostrProfile'; import '@components/Breadcrumbs'; +import '@components/ErrorView'; import '@components/Header'; -import '@routes/router'; import '@components/LoadingView'; +import '@components/NostrAvatar'; +import '@components/NostrProfile'; +import '@routes/router'; import type EveRouter from '@routes/router'; +import './style.css'; function checkRelayUp() { return new Promise((resolve, reject) => { diff --git a/src/routes/Home.ts b/src/routes/Home.ts index 081c5a7..929dddf 100644 --- a/src/routes/Home.ts +++ b/src/routes/Home.ts @@ -193,7 +193,7 @@ export class Home extends LitElement {
diff --git a/src/routes/Settings.ts b/src/routes/Settings.ts index eb146c9..ec13bab 100644 --- a/src/routes/Settings.ts +++ b/src/routes/Settings.ts @@ -1,5 +1,5 @@ -import defaultAvatar from '@/default-avatar.png'; import { getSigner, getUserProfile, ndk } from '@/ndk'; +import defaultAvatar from '@assets/default-avatar.png'; import type { ArxInputChangeEvent } from '@components/General/Input'; import { NDKEvent, type NDKUserProfile } from '@nostr-dev-kit/ndk'; import { LitElement, css, html } from 'lit'; diff --git a/src/routes/router.ts b/src/routes/router.ts index facd729..51410f4 100644 --- a/src/routes/router.ts +++ b/src/routes/router.ts @@ -1,4 +1,5 @@ import '@components/InitialSetup'; +import '@components/Sidebar'; import '@routes/404Page'; import '@routes/Arbor/Home'; import '@routes/Arbor/NewPost'; @@ -10,6 +11,8 @@ import '@routes/Profile'; import '@routes/Settings'; import '@routes/Wallet'; +import { getNpub, getUserProfile } from '@/ndk'; +import type { NDKUserProfile } from '@nostr-dev-kit/ndk'; import { spread } from '@open-wc/lit-helpers'; import { LitElement, css } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; @@ -103,11 +106,22 @@ export default class EveRouter extends LitElement { private beforeEachGuards: ((to: Route, from: Route | null) => boolean)[] = []; private afterEachHooks: ((to: Route, from: Route | null) => void)[] = []; + @state() + private userProfile: NDKUserProfile | undefined = undefined; + + @state() + private userNpub = ''; + static override styles = css` :host { - height: 100vh; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; display: grid; grid-template-rows: auto 1fr; + grid-template-columns: 100px 1fr; overflow: hidden; } @@ -131,9 +145,34 @@ export default class EveRouter extends LitElement { ::-webkit-scrollbar-thumb:hover { background: var(--color-neutral); } - + .window { overflow: auto; + grid-column: 2; + grid-row: 2; + position: relative; + } + + .window::after { + content: ''; + position: absolute; + right: 0; + top: 0; + width: 10px; + height: 100%; + opacity: 0; + pointer-events: none; + background: linear-gradient(to right, transparent, rgba(0, 0, 0, 0.03)); + transition: opacity 0.3s ease; + } + + .window:hover::after { + opacity: 1; + } + + arx-sidebar { + grid-column: 1; + grid-row: 1 / span 2; } .window-content { @@ -174,6 +213,11 @@ export default class EveRouter extends LitElement { .hide-overflow { overflow: hidden; } + + arx-header { + grid-column: 2; + grid-row: 1; + } `; constructor() { @@ -186,6 +230,9 @@ export default class EveRouter extends LitElement { override connectedCallback(): void { super.connectedCallback(); this.setupEventListeners(); + if (this.ccnSetup) { + this.loadUserProfile(); + } } override disconnectedCallback(): void { @@ -337,7 +384,7 @@ export default class EveRouter extends LitElement { renderSetup() { return html` -
+
this.finishSetup()} @@ -347,9 +394,25 @@ export default class EveRouter extends LitElement { `; } + async loadUserProfile() { + try { + this.userNpub = await getNpub(); + this.userProfile = await getUserProfile(); + } catch (error) { + console.error('Failed to load user profile:', error); + } + } + override render() { if (!this.ccnSetup) return this.renderSetup(); + return html` + this.navigate(e.detail)} + > 0} ?canGoForward=${this.currentIndex < this.history.length - 1}