diff --git a/server/shell.ts b/server/shell.ts index 022b414..9b31aca 100644 --- a/server/shell.ts +++ b/server/shell.ts @@ -840,11 +840,12 @@ export function renderShell(opts: ShellOptions): string { } } else { // No tabs left — show dashboard + const dashboard = document.querySelector('rstack-user-dashboard'); + if (dashboard && dashboard.setOpenTabs) dashboard.setOpenTabs([...layers]); tabBar.setLayers([]); tabBar.setAttribute('active', ''); currentModuleId = ''; if (tabCache) tabCache.hideAllPanes(); - const dashboard = document.querySelector('rstack-user-dashboard'); if (dashboard) { dashboard.style.display = ''; if (dashboard.refresh) dashboard.refresh(); } const app = document.getElementById('app'); if (app) app.classList.remove('canvas-layout'); @@ -995,8 +996,9 @@ export function renderShell(opts: ShellOptions): string { if (layers.length === 0) { // No tabs left — show the dashboard - if (tabCache) tabCache.hideAllPanes(); const dashboard = document.querySelector('rstack-user-dashboard'); + if (dashboard && dashboard.setOpenTabs) dashboard.setOpenTabs(closedLayer ? [closedLayer] : []); + if (tabCache) tabCache.hideAllPanes(); if (dashboard) { dashboard.style.display = ''; if (dashboard.refresh) dashboard.refresh(); diff --git a/shared/components/rstack-user-dashboard.ts b/shared/components/rstack-user-dashboard.ts index 069eabc..0b75880 100644 --- a/shared/components/rstack-user-dashboard.ts +++ b/shared/components/rstack-user-dashboard.ts @@ -1,24 +1,19 @@ /** - * — Shown when all tabs are closed. + * — Space-centric dashboard shown when all tabs are closed. * - * Displays a spaces grid (sorted by most recent visit), notifications panel, - * and quick-action buttons. Dispatches `dashboard-navigate` events to open - * rApps in new tabs. + * Sections: space header + stats, members, tools open, recent activity, + * active votes, and quick actions. * * Attributes: - * space — current space slug (for context) + * space — current space slug */ import { getSession, getAccessToken } from "./rstack-identity"; -interface SpaceInfo { - slug: string; - name: string; - icon?: string; - role?: string; - visibility?: string; - relationship?: string; - createdAt?: string; +interface MemberInfo { + did: string; + username: string; + displayName: string; } interface NotificationItem { @@ -31,14 +26,33 @@ interface NotificationItem { read: boolean; } -const RECENT_KEY = "rspace_recent_spaces"; +interface ProposalInfo { + id: string; + title: string; + status: string; + score: number; + vote_count: string; + created_at: string; +} + +interface TabLayer { + moduleId: string; + label?: string; + icon?: string; +} + +const CACHE_TTL = 30_000; // 30s export class RStackUserDashboard extends HTMLElement { #shadow: ShadowRoot; - #spaces: SpaceInfo[] = []; + #members: MemberInfo[] = []; #notifications: NotificationItem[] = []; - #loading = true; + #proposals: ProposalInfo[] = []; + #openTabs: TabLayer[] = []; + #membersLoading = true; #notifLoading = true; + #proposalsLoading = true; + #lastFetch = 0; constructor() { super(); @@ -59,37 +73,48 @@ export class RStackUserDashboard extends HTMLElement { } attributeChangedCallback() { + // Space changed — reset cache + this.#lastFetch = 0; this.#render(); } + /** Called from shell.ts before tabs are cleared */ + setOpenTabs(tabs: TabLayer[]) { + this.#openTabs = tabs; + } + /** Reload data when dashboard becomes visible again */ refresh() { - this.#loading = true; + if (Date.now() - this.#lastFetch < CACHE_TTL) { + this.#render(); + return; + } + this.#membersLoading = true; this.#notifLoading = true; + this.#proposalsLoading = true; this.#render(); this.#loadData(); } async #loadData() { - await Promise.all([this.#fetchSpaces(), this.#fetchNotifications()]); + this.#lastFetch = Date.now(); + await Promise.all([ + this.#fetchMembers(), + this.#fetchNotifications(), + this.#fetchProposals(), + ]); } - async #fetchSpaces() { - this.#loading = true; + async #fetchMembers() { + this.#membersLoading = true; try { - const headers: Record = {}; - const token = getAccessToken(); - if (token) headers["Authorization"] = `Bearer ${token}`; - - const res = await fetch("/api/spaces", { headers }); + const res = await fetch(`/${encodeURIComponent(this.space)}/api/space-members`); if (res.ok) { const data = await res.json(); - this.#spaces = data.spaces || []; + this.#members = data.members || []; } - } catch { - // Offline or API unavailable - } - this.#loading = false; + } catch { /* offline */ } + this.#membersLoading = false; this.#render(); } @@ -101,7 +126,6 @@ export class RStackUserDashboard extends HTMLElement { this.#render(); return; } - try { const res = await fetch("/api/notifications?limit=10", { headers: { Authorization: `Bearer ${token}` }, @@ -110,49 +134,28 @@ export class RStackUserDashboard extends HTMLElement { const data = await res.json(); this.#notifications = data.notifications || []; } - } catch { - // Silently fail - } + } catch { /* offline */ } this.#notifLoading = false; this.#render(); } - async #markAllRead() { - const token = getAccessToken(); - if (!token) return; - + async #fetchProposals() { + this.#proposalsLoading = true; try { - await fetch("/api/notifications/read-all", { - method: "POST", - headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" }, - body: "{}", - }); - } catch { /* best-effort */ } - - this.#notifications.forEach(n => n.read = true); - this.#render(); - } - - #getRecentVisits(): Record { - try { - const raw = localStorage.getItem(RECENT_KEY); - return raw ? JSON.parse(raw) : {}; + const slug = encodeURIComponent(this.space); + const res = await fetch(`/${slug}/rvote/api/proposals?space_slug=${slug}&limit=5`); + if (res.ok) { + const data = await res.json(); + this.#proposals = (data.proposals || []).filter( + (p: ProposalInfo) => p.status !== "completed" && p.status !== "rejected", + ); + } } catch { - return {}; + // rvote not installed or offline — hide section + this.#proposals = []; } - } - - #getSortedSpaces(): SpaceInfo[] { - const visits = this.#getRecentVisits(); - return [...this.#spaces].sort((a, b) => { - const va = visits[a.slug] || 0; - const vb = visits[b.slug] || 0; - if (va !== vb) return vb - va; // most recent first - // Fall back to createdAt - const ca = a.createdAt ? new Date(a.createdAt).getTime() : 0; - const cb = b.createdAt ? new Date(b.createdAt).getTime() : 0; - return cb - ca; - }); + this.#proposalsLoading = false; + this.#render(); } #timeAgo(iso: string): string { @@ -167,30 +170,16 @@ export class RStackUserDashboard extends HTMLElement { return `${Math.floor(days / 30)}mo ago`; } - #spaceInitial(space: SpaceInfo): string { - if (space.icon) return space.icon; - return (space.name || space.slug).charAt(0).toUpperCase(); - } - - #roleBadge(role?: string): string { - if (!role || role === "owner") return ""; - if (role === "admin") return `admin`; - if (role === "member") return `member`; - if (role === "viewer") return `view`; - return `${role}`; - } - - #dispatch(moduleId: string, spaceSlug?: string) { + #dispatch(moduleId: string) { this.dispatchEvent(new CustomEvent("dashboard-navigate", { bubbles: true, - detail: { moduleId, spaceSlug: spaceSlug || this.space }, + detail: { moduleId, spaceSlug: this.space }, })); } #parseActionUrl(url: string | null): { moduleId: string; spaceSlug?: string } | null { if (!url) return null; try { - // Handle relative URLs like /myspace/rnotes or /rnotes const parts = url.replace(/^\//, "").split("/"); if (parts.length >= 2) return { spaceSlug: parts[0], moduleId: parts[1] }; if (parts.length === 1 && parts[0]) return { moduleId: parts[0] }; @@ -198,91 +187,170 @@ export class RStackUserDashboard extends HTMLElement { return null; } + #memberInitial(m: MemberInfo): string { + return (m.displayName || m.username || "?").charAt(0).toUpperCase(); + } + + // Module icons for "tools open" chips + #moduleIcon(moduleId: string): string { + const icons: Record = { + rspace: "\u{1F30C}", rnotes: "\u{1F4DD}", rmeets: "\u{1F4F9}", rvote: "\u{1F5F3}", + rnetwork: "\u{1F465}", rcalendar: "\u{1F4C5}", rtasks: "\u{2705}", + rwallet: "\u{1F4B0}", rmail: "\u{1F4E7}", rmaps: "\u{1F5FA}", + rprompt: "\u{1F916}", rzine: "\u{1F4D6}", rfiles: "\u{1F4C1}", + }; + return icons[moduleId] || "\u{1F4E6}"; + } + + #moduleLabel(tab: TabLayer): string { + if (tab.label) return tab.label; + // Capitalize moduleId + const id = tab.moduleId; + return id.charAt(0).toUpperCase() + id.slice(1); + } + #render() { const session = getSession(); - const spaces = this.#getSortedSpaces(); - const visits = this.#getRecentVisits(); - const unreadCount = this.#notifications.filter(n => !n.read).length; + const space = this.space; + const spaceName = space.charAt(0).toUpperCase() + space.slice(1); - // ── Spaces grid ── - let spacesHTML: string; - if (this.#loading) { - spacesHTML = `
Loading spaces...
`; - } else if (spaces.length === 0) { - spacesHTML = `
No spaces found
`; - } else { - spacesHTML = spaces.map(s => { - const lastVisit = visits[s.slug]; - const timeLabel = lastVisit ? this.#timeAgo(new Date(lastVisit).toISOString()) : ""; - return ` - - `; - }).join(""); + // ── Stats pills ── + const statPills: string[] = []; + if (!this.#membersLoading) { + statPills.push(`${this.#members.length} member${this.#members.length !== 1 ? "s" : ""}`); + } + if (!this.#proposalsLoading && this.#proposals.length > 0) { + statPills.push(`${this.#proposals.length} active proposal${this.#proposals.length !== 1 ? "s" : ""}`); + } + if (this.#openTabs.length > 0) { + statPills.push(`${this.#openTabs.length} tab${this.#openTabs.length !== 1 ? "s" : ""} open`); } - // ── Notifications ── - let notifsHTML: string; - if (!session) { - notifsHTML = `
Sign in to see notifications
`; - } else if (this.#notifLoading) { - notifsHTML = `
Loading...
`; - } else if (this.#notifications.length === 0) { - notifsHTML = `
No notifications
`; + // ── Members ── + let membersHTML: string; + if (this.#membersLoading) { + membersHTML = `
Loading members...
`; + } else if (this.#members.length === 0) { + membersHTML = `
No members found
`; } else { - notifsHTML = this.#notifications.map(n => ` - `).join(""); } + // ── Tools open ── + let toolsHTML: string; + if (this.#openTabs.length === 0) { + toolsHTML = `
No tools were open
`; + } else { + toolsHTML = this.#openTabs.map(t => ` + + `).join(""); + } + + // ── Notifications / Recent Activity ── + let activityHTML: string; + if (!session) { + activityHTML = `
Sign in to see recent activity
`; + } else if (this.#notifLoading) { + activityHTML = `
Loading...
`; + } else if (this.#notifications.length === 0) { + activityHTML = `
No recent activity
`; + } else { + activityHTML = this.#notifications.map(n => ` + + `).join(""); + } + + // ── Active Votes ── + let votesHTML = ""; + if (!this.#proposalsLoading && this.#proposals.length > 0) { + votesHTML = ` +
+

Active Votes

+
+ ${this.#proposals.map(p => ` + + `).join("")} +
+
+ `; + } + + // ── Quick Actions ── + const hasProposals = this.#proposals.length > 0; + this.#shadow.innerHTML = `
-
-
-

Your Spaces

+
+
${spaceName.charAt(0)}
+
+

${spaceName}

+ ${statPills.length > 0 ? `
${statPills.join("")}
` : ""}
-
${spacesHTML}
-
-
-

Notifications ${unreadCount > 0 ? `${unreadCount}` : ""}

- ${unreadCount > 0 ? `` : ""} +
+
+

Members

+
${membersHTML}
+
+
+

Tools Open

+
${toolsHTML}
-
${notifsHTML}
-
+
+

Recent Activity

+
${activityHTML}
+
+ + ${votesHTML} + +

Quick Actions

+ ${hasProposals ? ` + + ` : ""}
@@ -293,40 +361,44 @@ export class RStackUserDashboard extends HTMLElement { } #attachEvents() { - // Space cards - this.#shadow.querySelectorAll(".space-card").forEach(el => { + // Member cards → open rNetwork + this.#shadow.querySelectorAll(".member-card").forEach(el => { el.addEventListener("click", () => { - const space = (el as HTMLElement).dataset.space!; - const mod = (el as HTMLElement).dataset.module || "rspace"; - this.#dispatch(mod, space); + this.#dispatch((el as HTMLElement).dataset.navigate || "rnetwork"); }); }); - // Notification items - this.#shadow.querySelectorAll(".notif-item").forEach(el => { + // Tool chips → re-open tab + this.#shadow.querySelectorAll(".tool-chip").forEach(el => { + el.addEventListener("click", () => { + this.#dispatch((el as HTMLElement).dataset.module!); + }); + }); + + // Activity items + this.#shadow.querySelectorAll(".activity-item").forEach(el => { el.addEventListener("click", () => { const url = (el as HTMLElement).dataset.actionUrl; const parsed = this.#parseActionUrl(url || null); if (parsed) { - this.#dispatch(parsed.moduleId, parsed.spaceSlug); + this.#dispatch(parsed.moduleId); } else { - // Default: open canvas in current space this.#dispatch("rspace"); } }); }); - // Mark all read - this.#shadow.getElementById("mark-all-read")?.addEventListener("click", (e) => { - e.stopPropagation(); - this.#markAllRead(); + // Vote items + this.#shadow.querySelectorAll(".vote-item").forEach(el => { + el.addEventListener("click", () => { + this.#dispatch((el as HTMLElement).dataset.navigate || "rvote"); + }); }); // Quick action buttons this.#shadow.querySelectorAll(".action-btn").forEach(el => { el.addEventListener("click", () => { - const mod = (el as HTMLElement).dataset.actionModule!; - this.#dispatch(mod); + this.#dispatch((el as HTMLElement).dataset.actionModule!); }); }); } @@ -343,23 +415,77 @@ export class RStackUserDashboard extends HTMLElement { const STYLES = ` :host { display: block; - width: 100%; - height: 100%; + position: fixed; + top: 92px; + left: 0; right: 0; bottom: 0; + overflow-y: auto; + background: var(--rs-bg, var(--rs-bg-page, #0f172a)); + z-index: 1; } .dashboard { - min-height: 100%; padding: 32px 24px; - overflow-y: auto; - background: var(--rs-bg, #0f172a); } .dashboard-inner { - max-width: 720px; + max-width: 800px; margin: 0 auto; display: flex; flex-direction: column; - gap: 32px; + gap: 28px; +} + +/* ── Space header ── */ + +.space-header { + display: flex; + align-items: center; + gap: 16px; +} + +.space-icon { + width: 48px; + height: 48px; + border-radius: 12px; + background: linear-gradient(135deg, rgba(6,182,212,0.2), rgba(94,234,212,0.12)); + color: var(--rs-accent, #06b6d4); + display: flex; + align-items: center; + justify-content: center; + font-size: 1.3rem; + font-weight: 700; + flex-shrink: 0; +} + +.space-title { + margin: 0; + font-size: 1.25rem; + font-weight: 700; + color: var(--rs-text-primary, #e2e8f0); +} + +.stat-row { + display: flex; + gap: 8px; + margin-top: 4px; + flex-wrap: wrap; +} + +.stat-pill { + font-size: 0.7rem; + padding: 2px 8px; + border-radius: 10px; + background: var(--rs-bg-surface, rgba(255,255,255,0.06)); + color: var(--rs-text-muted, #94a3b8); + white-space: nowrap; +} + +/* ── Two-column grid ── */ + +.two-col { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 20px; } /* ── Sections ── */ @@ -368,137 +494,28 @@ const STYLES = ` display: flex; align-items: center; justify-content: space-between; - margin-bottom: 12px; + margin-bottom: 10px; } .section-header h2 { margin: 0; - font-size: 0.95rem; + font-size: 0.85rem; font-weight: 600; - color: var(--rs-text-primary, #e2e8f0); - display: flex; - align-items: center; - gap: 8px; + color: var(--rs-text-secondary, #cbd5e1); + text-transform: uppercase; + letter-spacing: 0.04em; } .section-empty { - padding: 24px 16px; + padding: 20px 16px; text-align: center; color: var(--rs-text-muted, #94a3b8); - font-size: 0.8rem; + font-size: 0.78rem; } -.unread-badge { - display: inline-flex; - align-items: center; - justify-content: center; - min-width: 18px; - height: 18px; - padding: 0 5px; - border-radius: 9px; - background: #ef4444; - color: white; - font-size: 0.7rem; - font-weight: 700; -} +/* ── Members ── */ -.mark-all-btn { - background: none; - border: none; - color: var(--rs-accent, #06b6d4); - font-size: 0.75rem; - cursor: pointer; - padding: 4px 8px; - border-radius: 4px; - transition: background 0.15s; -} -.mark-all-btn:hover { - background: var(--rs-bg-hover, rgba(255,255,255,0.05)); -} - -/* ── Spaces grid ── */ - -.spaces-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); - gap: 10px; -} - -.space-card { - display: flex; - align-items: center; - gap: 12px; - padding: 14px 16px; - border-radius: 10px; - background: var(--rs-bg-surface, #1e293b); - border: 1px solid var(--rs-border, rgba(255,255,255,0.08)); - cursor: pointer; - transition: background 0.15s, border-color 0.15s, transform 0.1s; - text-align: left; - color: inherit; - font: inherit; -} -.space-card:hover { - background: var(--rs-bg-hover, rgba(255,255,255,0.06)); - border-color: var(--rs-accent, #06b6d4); - transform: translateY(-1px); -} - -.space-icon { - width: 40px; - height: 40px; - border-radius: 10px; - background: linear-gradient(135deg, rgba(6,182,212,0.15), rgba(94,234,212,0.1)); - color: var(--rs-accent, #06b6d4); - display: flex; - align-items: center; - justify-content: center; - font-size: 1.1rem; - font-weight: 700; - flex-shrink: 0; -} - -.space-info { - min-width: 0; - flex: 1; -} - -.space-name { - font-size: 0.85rem; - font-weight: 600; - color: var(--rs-text-primary, #e2e8f0); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.space-meta { - display: flex; - align-items: center; - gap: 6px; - margin-top: 3px; -} - -.space-time { - font-size: 0.7rem; - color: var(--rs-text-muted, #64748b); -} - -.role-badge { - font-size: 0.65rem; - padding: 1px 5px; - border-radius: 4px; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.02em; -} -.role-admin { background: rgba(251,191,36,0.15); color: #fbbf24; } -.role-member { background: rgba(96,165,250,0.15); color: #60a5fa; } -.role-viewer { background: rgba(148,163,184,0.15); color: #94a3b8; } - -/* ── Notifications ── */ - -.notifs-list { +.members-list { display: flex; flex-direction: column; border-radius: 10px; @@ -507,7 +524,98 @@ const STYLES = ` overflow: hidden; } -.notif-item { +.member-card { + display: flex; + align-items: center; + gap: 10px; + padding: 10px 14px; + cursor: pointer; + transition: background 0.15s; + border: none; + border-bottom: 1px solid var(--rs-border, rgba(255,255,255,0.06)); + background: none; + text-align: left; + color: inherit; + font: inherit; + width: 100%; +} +.member-card:last-child { border-bottom: none; } +.member-card:hover { + background: var(--rs-bg-hover, rgba(255,255,255,0.05)); +} + +.member-avatar { + width: 32px; + height: 32px; + border-radius: 50%; + background: linear-gradient(135deg, rgba(6,182,212,0.2), rgba(94,234,212,0.12)); + color: var(--rs-accent, #06b6d4); + display: flex; + align-items: center; + justify-content: center; + font-size: 0.8rem; + font-weight: 700; + flex-shrink: 0; +} + +.member-info { min-width: 0; } + +.member-name { + font-size: 0.8rem; + font-weight: 600; + color: var(--rs-text-primary, #e2e8f0); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.member-handle { + font-size: 0.7rem; + color: var(--rs-text-muted, #64748b); +} + +/* ── Tools open ── */ + +.tools-grid { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.tool-chip { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 8px 14px; + border-radius: 8px; + background: var(--rs-bg-surface, #1e293b); + border: 1px solid var(--rs-border, rgba(255,255,255,0.08)); + color: var(--rs-text-primary, #e2e8f0); + font-size: 0.78rem; + font-weight: 500; + cursor: pointer; + transition: background 0.15s, border-color 0.15s; + font: inherit; +} +.tool-chip:hover { + background: var(--rs-bg-hover, rgba(255,255,255,0.06)); + border-color: var(--rs-accent, #06b6d4); +} + +.tool-icon { font-size: 0.95rem; } + +/* ── Recent Activity ── */ + +.activity-list { + display: flex; + flex-direction: column; + border-radius: 10px; + background: var(--rs-bg-surface, #1e293b); + border: 1px solid var(--rs-border, rgba(255,255,255,0.08)); + overflow: hidden; +} + +.activity-item { display: flex; align-items: flex-start; justify-content: space-between; @@ -515,40 +623,35 @@ const STYLES = ` padding: 10px 16px; cursor: pointer; transition: background 0.15s; + border: none; border-bottom: 1px solid var(--rs-border, rgba(255,255,255,0.06)); background: none; - border-left: none; - border-right: none; - border-top: none; text-align: left; color: inherit; font: inherit; width: 100%; } -.notif-item:last-child { border-bottom: none; } -.notif-item:hover { +.activity-item:last-child { border-bottom: none; } +.activity-item:hover { background: var(--rs-bg-hover, rgba(255,255,255,0.05)); } -.notif-item.unread { +.activity-item.unread { background: rgba(6,182,212,0.04); } -.notif-item.unread .notif-title { +.activity-item.unread .activity-title { font-weight: 600; } -.notif-content { - flex: 1; - min-width: 0; -} +.activity-content { flex: 1; min-width: 0; } -.notif-title { +.activity-title { font-size: 0.8rem; color: var(--rs-text-primary, #e2e8f0); line-height: 1.3; } -.notif-body { - font-size: 0.75rem; +.activity-body { + font-size: 0.73rem; color: var(--rs-text-muted, #94a3b8); margin-top: 2px; line-height: 1.3; @@ -557,7 +660,7 @@ const STYLES = ` white-space: nowrap; } -.notif-time { +.activity-time { font-size: 0.7rem; color: var(--rs-text-muted, #64748b); flex-shrink: 0; @@ -565,6 +668,77 @@ const STYLES = ` margin-top: 2px; } +/* ── Active Votes ── */ + +.votes-list { + display: flex; + flex-direction: column; + border-radius: 10px; + background: var(--rs-bg-surface, #1e293b); + border: 1px solid var(--rs-border, rgba(255,255,255,0.08)); + overflow: hidden; +} + +.vote-item { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; + padding: 10px 16px; + cursor: pointer; + transition: background 0.15s; + border: none; + border-bottom: 1px solid var(--rs-border, rgba(255,255,255,0.06)); + background: none; + text-align: left; + color: inherit; + font: inherit; + width: 100%; +} +.vote-item:last-child { border-bottom: none; } +.vote-item:hover { + background: var(--rs-bg-hover, rgba(255,255,255,0.05)); +} + +.vote-info { flex: 1; min-width: 0; } + +.vote-title { + font-size: 0.8rem; + font-weight: 600; + color: var(--rs-text-primary, #e2e8f0); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.vote-meta { + display: flex; + gap: 8px; + margin-top: 3px; +} + +.vote-status { + font-size: 0.65rem; + padding: 1px 6px; + border-radius: 4px; + background: rgba(251,191,36,0.15); + color: #fbbf24; + font-weight: 600; + text-transform: uppercase; +} + +.vote-count { + font-size: 0.7rem; + color: var(--rs-text-muted, #94a3b8); +} + +.vote-score { + font-size: 0.85rem; + font-weight: 700; + color: var(--rs-accent, #06b6d4); + flex-shrink: 0; +} + /* ── Quick Actions ── */ .actions-row { @@ -593,18 +767,22 @@ const STYLES = ` border-color: var(--rs-accent, #06b6d4); } -.action-icon { - font-size: 1rem; -} +.action-icon { font-size: 1rem; } /* ── Responsive ── */ +@media (max-width: 640px) { + :host { + position: static; + overflow-y: visible; + } + .dashboard { padding: 20px 12px; } + .two-col { grid-template-columns: 1fr; } +} + @media (max-width: 480px) { - .dashboard { - padding: 20px 12px; - } - .spaces-grid { - grid-template-columns: 1fr; - } + .dashboard { padding: 16px 8px; } + .actions-row { flex-direction: column; } + .action-btn { justify-content: center; } } `; diff --git a/website/public/shell.css b/website/public/shell.css index 761af07..70a4d96 100644 --- a/website/public/shell.css +++ b/website/public/shell.css @@ -347,6 +347,7 @@ body { .rstack-tab-row, #app, +rstack-user-dashboard, .rspace-iframe-wrap, #toolbar { transition: margin-left 0.25s ease, left 0.25s ease; @@ -360,6 +361,10 @@ body.rstack-sidebar-open #app { margin-left: 280px; } +body.rstack-sidebar-open rstack-user-dashboard { + left: 280px; +} + body.rstack-sidebar-open .rspace-iframe-wrap { left: 280px; } @@ -457,6 +462,7 @@ body.rstack-sidebar-open #toolbar { /* Sidebar overlays on mobile — no push offsets */ body.rstack-sidebar-open .rstack-tab-row { left: 0; } body.rstack-sidebar-open #app { margin-left: 0; } + body.rstack-sidebar-open rstack-user-dashboard { left: 0; } body.rstack-sidebar-open .rspace-iframe-wrap { left: 0; } body.rstack-sidebar-open #toolbar { left: 12px; } }