From abb3f57ca027d8edea72018c67e8901f6be1ccfd Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Wed, 4 Mar 2026 10:30:20 -0800 Subject: [PATCH] fix: space settings panel hidden behind canvas due to backdrop-filter containment Move outside the header element so its position:fixed is relative to the viewport instead of the 56px header (backdrop-filter creates a containing block for fixed descendants). Bump panel z-index above all canvas elements. Also migrate hardcoded colors to CSS theme variables across shell components. Co-Authored-By: Claude Opus 4.6 --- modules/rsocials/mod.ts | 68 +++++++------- server/output-list.ts | 18 ++-- server/shell.ts | 6 +- shared/components/rstack-app-switcher.ts | 88 +++++++++++++------ shared/components/rstack-identity.ts | 77 ++++++++-------- shared/components/rstack-notification-bell.ts | 10 +-- shared/components/rstack-space-settings.ts | 54 ++++++------ shared/components/rstack-space-switcher.ts | 72 +++++++-------- shared/components/rstack-tab-bar.ts | 52 ++++++----- website/public/shell.css | 24 +++++ 10 files changed, 267 insertions(+), 202 deletions(-) diff --git a/modules/rsocials/mod.ts b/modules/rsocials/mod.ts index f046d39..7eacbec 100644 --- a/modules/rsocials/mod.ts +++ b/modules/rsocials/mod.ts @@ -649,32 +649,32 @@ const THREAD_CSS = ` .thread-btn { padding: 0.5rem 1rem; border-radius: 8px; border: none; font-size: 0.85rem; font-weight: 600; cursor: pointer; transition: all 0.15s; } .thread-btn--primary { background: #6366f1; color: white; } .thread-btn--primary:hover { background: #818cf8; } -.thread-btn--outline { background: transparent; color: #94a3b8; border: 1px solid #334155; } +.thread-btn--outline { background: transparent; color: var(--rs-text-secondary); border: 1px solid var(--rs-input-border); } .thread-btn--outline:hover { border-color: #6366f1; color: #c4b5fd; } .thread-btn--success { background: #10b981; color: white; } .thread-btn--success:hover { background: #34d399; } .thread-btn:disabled { opacity: 0.5; cursor: not-allowed; } .thread-compose { position: sticky; top: 1rem; align-self: start; display: flex; flex-direction: column; gap: 1rem; } .thread-compose__textarea { - width: 100%; min-height: 320px; background: #1e293b; color: #e2e8f0; border: 1px solid #334155; + width: 100%; min-height: 320px; background: var(--rs-bg-surface); color: var(--rs-text-primary); border: 1px solid var(--rs-input-border); border-radius: 0.75rem; padding: 1rem; font-family: inherit; font-size: 0.9rem; resize: vertical; line-height: 1.6; box-sizing: border-box; } .thread-compose__textarea:focus { outline: none; border-color: #6366f1; } -.thread-compose__textarea::placeholder { color: #475569; } +.thread-compose__textarea::placeholder { color: var(--rs-text-muted); } .thread-compose__fields { display: flex; gap: 0.75rem; } .thread-compose__input { - flex: 1; background: #1e293b; color: #e2e8f0; border: 1px solid #334155; + flex: 1; background: var(--rs-bg-surface); color: var(--rs-text-primary); border: 1px solid var(--rs-input-border); border-radius: 8px; padding: 0.5rem 0.75rem; font-size: 0.85rem; box-sizing: border-box; } .thread-compose__input:focus { outline: none; border-color: #6366f1; } -.thread-compose__input::placeholder { color: #475569; } +.thread-compose__input::placeholder { color: var(--rs-text-muted); } .thread-compose__title { - width: 100%; background: #1e293b; color: #e2e8f0; border: 1px solid #334155; + width: 100%; background: var(--rs-bg-surface); color: var(--rs-text-primary); border: 1px solid var(--rs-input-border); border-radius: 8px; padding: 0.5rem 0.75rem; font-size: 0.85rem; box-sizing: border-box; } .thread-compose__title:focus { outline: none; border-color: #6366f1; } -.thread-compose__title::placeholder { color: #475569; } +.thread-compose__title::placeholder { color: var(--rs-text-muted); } .thread-drafts { grid-column: 1 / -1; } .thread-drafts__toggle { cursor: pointer; user-select: none; } .thread-drafts__list { @@ -682,24 +682,24 @@ const THREAD_CSS = ` margin-top: 0.75rem; } .thread-drafts__list[hidden] { display: none; } -.thread-drafts__empty { color: #475569; font-size: 0.8rem; padding: 0.5rem 0; } +.thread-drafts__empty { color: var(--rs-text-muted); font-size: 0.8rem; padding: 0.5rem 0; } .thread-draft-item { display: flex; align-items: center; gap: 0.5rem; padding: 0.5rem 0.75rem; - background: #1e293b; border: 1px solid #334155; border-radius: 8px; + background: var(--rs-bg-surface); border: 1px solid var(--rs-input-border); border-radius: 8px; transition: border-color 0.15s; cursor: pointer; } .thread-draft-item:hover { border-color: #6366f1; } .thread-draft-item--active { border-color: #6366f1; background: rgba(99,102,241,0.1); } .thread-draft-item__info { flex: 1; min-width: 0; } -.thread-draft-item__info strong { display: block; font-size: 0.8rem; color: #e2e8f0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } -.thread-draft-item__info span { font-size: 0.7rem; color: #64748b; } +.thread-draft-item__info strong { display: block; font-size: 0.8rem; color: var(--rs-text-primary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } +.thread-draft-item__info span { font-size: 0.7rem; color: var(--rs-text-muted); } .thread-draft-item__delete { - background: none; border: none; color: #64748b; font-size: 1.2rem; cursor: pointer; + background: none; border: none; color: var(--rs-text-muted); font-size: 1.2rem; cursor: pointer; padding: 0 4px; line-height: 1; flex-shrink: 0; } .thread-draft-item__delete:hover { color: #ef4444; } .thread-image-section { display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap; } -.thread-image-preview { border-radius: 8px; overflow: hidden; border: 1px solid #334155; } +.thread-image-preview { border-radius: 8px; overflow: hidden; border: 1px solid var(--rs-input-border); } .thread-image-preview[hidden] { display: none; } .thread-image-preview img { display: block; max-width: 200px; height: auto; } #share-link-area { grid-column: 1 / -1; } @@ -710,20 +710,20 @@ const THREAD_CSS = ` } .thread-share-link code { font-size: 0.75rem; color: #7dd3fc; } .thread-share-link button { - background: none; border: none; color: #94a3b8; cursor: pointer; font-size: 0.75rem; padding: 2px 6px; + background: none; border: none; color: var(--rs-text-secondary); cursor: pointer; font-size: 0.75rem; padding: 2px 6px; } -.thread-share-link button:hover { color: #e2e8f0; } +.thread-share-link button:hover { color: var(--rs-text-primary); } .thread-preview { display: flex; flex-direction: column; gap: 0; } -.thread-preview__empty { color: #475569; text-align: center; padding: 3rem 1rem; font-size: 0.9rem; } +.thread-preview__empty { color: var(--rs-text-muted); text-align: center; padding: 3rem 1rem; font-size: 0.9rem; } .tweet-card { - position: relative; background: #1e293b; border: 1px solid #334155; border-radius: 0.75rem; + position: relative; background: var(--rs-bg-surface); border: 1px solid var(--rs-input-border); border-radius: 0.75rem; padding: 1rem; margin-bottom: 0; } .tweet-card + .tweet-card { border-top-left-radius: 0; border-top-right-radius: 0; margin-top: -1px; } .tweet-card:has(+ .tweet-card) { border-bottom-left-radius: 0; border-bottom-right-radius: 0; } .tweet-card__connector { position: absolute; left: 29px; top: -1px; width: 2px; height: 1rem; - background: #334155; z-index: 1; + background: var(--rs-input-border); z-index: 1; } .tweet-card__header { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.5rem; } .tweet-card__avatar { @@ -731,16 +731,16 @@ const THREAD_CSS = ` display: flex; align-items: center; justify-content: center; color: white; font-weight: 700; font-size: 1rem; flex-shrink: 0; } -.tweet-card__name { font-weight: 700; color: #f1f5f9; font-size: 0.9rem; } -.tweet-card__handle { color: #64748b; font-size: 0.85rem; } -.tweet-card__dot { color: #64748b; font-size: 0.85rem; } -.tweet-card__time { color: #64748b; font-size: 0.85rem; } -.tweet-card__content { color: #e2e8f0; font-size: 0.95rem; line-height: 1.6; margin: 0 0 0.75rem; white-space: pre-wrap; word-break: break-word; } +.tweet-card__name { font-weight: 700; color: var(--rs-text-primary); font-size: 0.9rem; } +.tweet-card__handle { color: var(--rs-text-muted); font-size: 0.85rem; } +.tweet-card__dot { color: var(--rs-text-muted); font-size: 0.85rem; } +.tweet-card__time { color: var(--rs-text-muted); font-size: 0.85rem; } +.tweet-card__content { color: var(--rs-text-primary); font-size: 0.95rem; line-height: 1.6; margin: 0 0 0.75rem; white-space: pre-wrap; word-break: break-word; } .tweet-card__footer { display: flex; align-items: center; justify-content: space-between; } .tweet-card__actions { display: flex; gap: 1.25rem; } -.tweet-card__action { display: flex; align-items: center; gap: 0.3rem; color: #64748b; font-size: 0.8rem; cursor: default; } +.tweet-card__action { display: flex; align-items: center; gap: 0.3rem; color: var(--rs-text-muted); font-size: 0.8rem; cursor: default; } .tweet-card__action svg { width: 16px; height: 16px; } -.tweet-card__meta { display: flex; align-items: center; gap: 0.75rem; font-size: 0.75rem; color: #64748b; } +.tweet-card__meta { display: flex; align-items: center; gap: 0.75rem; font-size: 0.75rem; color: var(--rs-text-muted); } .tweet-card__chars { font-variant-numeric: tabular-nums; } .tweet-card__chars--over { color: #ef4444; font-weight: 600; } .tweet-card__thread-num { color: #6366f1; font-weight: 600; } @@ -751,18 +751,18 @@ const THREAD_CSS = ` .thread-export-dropdown { position: relative; } .thread-export-menu { position: absolute; top: calc(100% + 4px); right: 0; z-index: 100; - background: #1e293b; border: 1px solid #334155; border-radius: 8px; + background: var(--rs-bg-surface); border: 1px solid var(--rs-input-border); border-radius: 8px; min-width: 180px; overflow: hidden; - box-shadow: 0 8px 24px rgba(0,0,0,0.4); + box-shadow: 0 8px 24px var(--rs-shadow-lg); } .thread-export-menu[hidden] { display: none; } .thread-export-menu button { display: block; width: 100%; padding: 0.6rem 0.75rem; border: none; - background: transparent; color: #e2e8f0; font-size: 0.85rem; + background: transparent; color: var(--rs-text-primary); font-size: 0.85rem; text-align: left; cursor: pointer; transition: background 0.1s; } .thread-export-menu button:hover { background: rgba(99,102,241,0.15); } -.thread-export-menu button + button { border-top: 1px solid rgba(255,255,255,0.05); } +.thread-export-menu button + button { border-top: 1px solid var(--rs-bg-hover); } `; function renderThreadBuilderPage(space: string, threadData?: ThreadData | null): string { @@ -1404,11 +1404,11 @@ const THREAD_RO_CSS = ` .thread-ro { max-width: 640px; margin: 0 auto; padding: 2rem 1rem; } .thread-ro__header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 1.5rem; flex-wrap: wrap; gap: 1rem; } .thread-ro__author { display: flex; align-items: center; gap: 0.75rem; } -.thread-ro__name { font-weight: 700; color: #f1f5f9; font-size: 1.1rem; } -.thread-ro__handle { color: #64748b; font-size: 0.9rem; } -.thread-ro__meta { display: flex; align-items: center; gap: 0.5rem; color: #64748b; font-size: 0.85rem; } -.thread-ro__title { font-size: 1.4rem; color: #f1f5f9; margin: 0 0 1.5rem; line-height: 1.3; } -.thread-ro__image { margin-bottom: 1.5rem; border-radius: 12px; overflow: hidden; border: 1px solid #334155; } +.thread-ro__name { font-weight: 700; color: var(--rs-text-primary); font-size: 1.1rem; } +.thread-ro__handle { color: var(--rs-text-muted); font-size: 0.9rem; } +.thread-ro__meta { display: flex; align-items: center; gap: 0.5rem; color: var(--rs-text-muted); font-size: 0.85rem; } +.thread-ro__title { font-size: 1.4rem; color: var(--rs-text-primary); margin: 0 0 1.5rem; line-height: 1.3; } +.thread-ro__image { margin-bottom: 1.5rem; border-radius: 12px; overflow: hidden; border: 1px solid var(--rs-input-border); } .thread-ro__image img { display: block; width: 100%; height: auto; } .thread-ro__cards { margin-bottom: 1.5rem; } .thread-ro__actions { display: flex; gap: 0.5rem; flex-wrap: wrap; margin-bottom: 2rem; padding-bottom: 2rem; border-bottom: 1px solid #334155; } diff --git a/server/output-list.ts b/server/output-list.ts index 17013a7..bc24164 100644 --- a/server/output-list.ts +++ b/server/output-list.ts @@ -105,20 +105,20 @@ export function renderOutputListPage( .output-list { max-width: 960px; margin: 0 auto; padding: 2rem 1rem; } .output-list__header { display: flex; align-items: center; gap: 1rem; margin-bottom: 2rem; } .output-list__icon { font-size: 2.5rem; } -.output-list__title { margin: 0; font-size: 1.5rem; color: #f1f5f9; } -.output-list__desc { margin: 0.25rem 0 0; color: #94a3b8; font-size: 0.95rem; } +.output-list__title { margin: 0; font-size: 1.5rem; color: var(--rs-text-primary); } +.output-list__desc { margin: 0.25rem 0 0; color: var(--rs-text-secondary); font-size: 0.95rem; } .output-list__grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 1rem; } -.output-list__loading { color: #64748b; text-align: center; padding: 3rem 0; grid-column: 1 / -1; } -.output-list__empty { text-align: center; padding: 4rem 1rem; grid-column: 1 / -1; color: #64748b; } +.output-list__loading { color: var(--rs-text-muted); text-align: center; padding: 3rem 0; grid-column: 1 / -1; } +.output-list__empty { text-align: center; padding: 4rem 1rem; grid-column: 1 / -1; color: var(--rs-text-muted); } .output-list__empty-icon { font-size: 3rem; margin-bottom: 0.5rem; } .output-card { display: block; - background: #1e293b; - border: 1px solid #334155; + background: var(--rs-bg-surface); + border: 1px solid var(--rs-bg-surface-raised); border-radius: 0.75rem; padding: 1.25rem; text-decoration: none; @@ -126,9 +126,9 @@ export function renderOutputListPage( transition: border-color 0.15s, transform 0.15s; } .output-card:hover { border-color: #6366f1; transform: translateY(-2px); } -.output-card__title { font-size: 1.05rem; font-weight: 600; color: #f1f5f9; margin-bottom: 0.5rem; } -.output-card__desc { font-size: 0.875rem; color: #94a3b8; line-height: 1.4; margin-bottom: 0.5rem; } -.output-card__date { font-size: 0.75rem; color: #64748b; } +.output-card__title { font-size: 1.05rem; font-weight: 600; color: var(--rs-text-primary); margin-bottom: 0.5rem; } +.output-card__desc { font-size: 0.875rem; color: var(--rs-text-secondary); line-height: 1.4; margin-bottom: 0.5rem; } +.output-card__date { font-size: 0.75rem; color: var(--rs-text-muted); } `; return renderShell({ diff --git a/server/shell.ts b/server/shell.ts index f2198cb..dd7a8a5 100644 --- a/server/shell.ts +++ b/server/shell.ts @@ -105,8 +105,7 @@ export function renderShell(opts: ShellOptions): string {
- ${spaceEncrypted ? '🔒' : ''} - + ${spaceEncrypted ? '🔒' : ''}
@@ -117,6 +116,7 @@ export function renderShell(opts: ShellOptions): string {
+
@@ -625,7 +625,7 @@ const ACCESS_GATE_CSS = ` #rspace-access-gate { position: fixed; inset: 0; z-index: 9999; display: flex; align-items: center; justify-content: center; - background: rgba(15, 23, 42, 0.95); backdrop-filter: blur(8px); + background: var(--rs-bg-overlay); backdrop-filter: blur(8px); } .access-gate__card { text-align: center; color: var(--rs-text-primary); max-width: 400px; padding: 2rem; diff --git a/shared/components/rstack-app-switcher.ts b/shared/components/rstack-app-switcher.ts index 5aa8d7e..232b6b7 100644 --- a/shared/components/rstack-app-switcher.ts +++ b/shared/components/rstack-app-switcher.ts @@ -1,5 +1,5 @@ /** - * — Dropdown to switch between rSpace modules. + * — Collapsible left sidebar to switch between rSpace modules. * * Attributes: * current — the active module ID (highlighted) @@ -236,29 +236,35 @@ export class RStackAppSwitcher extends HTMLElement {
- `; const trigger = this.#shadow.getElementById("trigger")!; - const menu = this.#shadow.getElementById("menu")!; + const sidebar = this.#shadow.getElementById("sidebar")!; + const collapse = this.#shadow.getElementById("collapse")!; + + const open = () => { + sidebar.classList.add("open"); + document.body.classList.add("rstack-sidebar-open"); + }; + const close = () => { + sidebar.classList.remove("open"); + document.body.classList.remove("rstack-sidebar-open"); + }; + const toggle = () => { + if (sidebar.classList.contains("open")) close(); else open(); + }; trigger.addEventListener("click", (e) => { e.stopPropagation(); - const isOpen = menu.classList.toggle("open"); - if (isOpen) { - const rect = trigger.getBoundingClientRect(); - menu.style.top = `${rect.bottom + 6}px`; - menu.style.left = `${Math.max(4, rect.left)}px`; - } + toggle(); }); - // Prevent external links from closing the menu prematurely - this.#shadow.querySelectorAll(".item-ext").forEach((el) => { - el.addEventListener("click", (e) => e.stopPropagation()); - }); + collapse.addEventListener("click", () => close()); // Intercept same-origin module links → dispatch event for tab system this.#shadow.querySelectorAll("a.item").forEach((el) => { @@ -272,7 +278,6 @@ export class RStackAppSwitcher extends HTMLElement { if (url.origin !== window.location.origin) return; } catch { return; } e.preventDefault(); - menu.classList.remove("open"); this.dispatchEvent(new CustomEvent("module-select", { detail: { moduleId }, bubbles: true, @@ -280,8 +285,6 @@ export class RStackAppSwitcher extends HTMLElement { })); }); }); - - document.addEventListener("click", () => menu.classList.remove("open")); } #getSpaceSlug(): string { @@ -313,7 +316,7 @@ const STYLES = ` .trigger-badge { display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px; border-radius: 5px; - font-size: 0.6rem; font-weight: 900; color: #0f172a; + font-size: 0.6rem; font-weight: 900; color: var(--rs-text-inverse); line-height: 1; flex-shrink: 0; } .trigger-badge.rstack-gradient { @@ -322,14 +325,42 @@ const STYLES = ` .caret { font-size: 0.7em; opacity: 0.6; } -.menu { - position: fixed; margin-top: 0; - min-width: 300px; border-radius: 12px; overflow: hidden; - overflow-y: auto; max-height: 70vh; - box-shadow: 0 8px 30px rgba(0,0,0,0.25); display: none; z-index: 10001; - background: var(--rs-bg-surface); border: 1px solid var(--rs-border); +/* ── Sidebar panel ── */ +.sidebar { + position: fixed; + top: 56px; left: 0; bottom: 0; + width: 280px; + overflow-y: auto; + z-index: 10001; + background: var(--rs-bg-surface); + border-right: 1px solid var(--rs-border); + box-shadow: var(--rs-shadow-lg); + transform: translateX(-100%); + transition: transform 0.25s ease; +} +.sidebar.open { + transform: translateX(0); +} + +/* Collapse button */ +.collapse-btn { + position: absolute; + top: 8px; right: 8px; + width: 28px; height: 28px; + border-radius: 6px; + border: 1px solid var(--rs-border); + background: var(--rs-bg-surface); + color: var(--rs-text-secondary); + font-size: 1rem; line-height: 1; + cursor: pointer; + display: flex; align-items: center; justify-content: center; + transition: background 0.15s, color 0.15s; + z-index: 1; +} +.collapse-btn:hover { + background: var(--rs-bg-hover); + color: var(--rs-text-primary); } -.menu.open { display: block; } /* rStack header */ a.rstack-header { @@ -343,7 +374,7 @@ a.rstack-header:hover { background: var(--rs-bg-hover); } display: flex; align-items: center; justify-content: center; width: 28px; height: 28px; border-radius: 8px; background: linear-gradient(135deg, #67e8f9, #c4b5fd, #fda4af); - font-size: 0.7rem; font-weight: 900; color: #0f172a; line-height: 1; + font-size: 0.7rem; font-weight: 900; color: var(--rs-text-inverse); line-height: 1; flex-shrink: 0; } .rstack-info { display: flex; flex-direction: column; } @@ -388,7 +419,7 @@ a.rstack-header:hover { background: var(--rs-bg-hover); } .item-badge { display: flex; align-items: center; justify-content: center; width: 28px; height: 28px; border-radius: 6px; - font-size: 0.6rem; font-weight: 900; color: #0f172a; + font-size: 0.6rem; font-weight: 900; color: var(--rs-text-inverse); line-height: 1; flex-shrink: 0; } .item-icon { font-size: 1.3rem; width: 28px; text-align: center; flex-shrink: 0; } @@ -413,4 +444,9 @@ a.rstack-header:hover { background: var(--rs-bg-hover); } border-top: 1px solid var(--rs-border-subtle); margin-top: 4px; padding-top: 10px; } + +/* Mobile: sidebar overlays instead of pushing */ +@media (max-width: 640px) { + .sidebar { box-shadow: 4px 0 20px rgba(0,0,0,0.3); } +} `; diff --git a/shared/components/rstack-identity.ts b/shared/components/rstack-identity.ts index a7dedb0..7dc954f 100644 --- a/shared/components/rstack-identity.ts +++ b/shared/components/rstack-identity.ts @@ -710,7 +710,7 @@ export class RStackIdentity extends HTMLElement { if (emailStep === "input") { body = `