refactor: standardize module component UI across all rApps

Consistent nav headers, button styles, and layout patterns
across calendar, cart, choices, data, forum, funds, inbox,
maps, network, notes, providers, trips, vote, and work modules.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-02-25 15:47:58 -08:00
parent e7ce57ce0b
commit b299caf433
17 changed files with 229 additions and 203 deletions

View File

@ -79,13 +79,13 @@ class FolkCalendarView extends HTMLElement {
:host { display: block; font-family: system-ui, -apple-system, sans-serif; color: #e0e0e0; }
* { box-sizing: border-box; }
.header { display: flex; gap: 8px; margin-bottom: 16px; align-items: center; }
.nav-btn { padding: 6px 12px; border-radius: 6px; border: 1px solid #444; background: #1e1e2e; color: #ccc; cursor: pointer; font-size: 16px; }
.nav-btn:hover { border-color: #666; }
.header-title { font-size: 18px; font-weight: 600; flex: 1; text-align: center; }
.toggle-btn { padding: 6px 12px; border-radius: 6px; border: 1px solid #444; background: #1e1e2e; color: #ccc; cursor: pointer; font-size: 12px; }
.rapp-nav { display: flex; gap: 8px; margin-bottom: 16px; align-items: center; min-height: 36px; }
.rapp-nav__back { padding: 4px 10px; border-radius: 6px; border: 1px solid rgba(255,255,255,0.1); background: transparent; color: #94a3b8; cursor: pointer; font-size: 16px; }
.rapp-nav__back:hover { color: #e2e8f0; border-color: rgba(255,255,255,0.2); }
.rapp-nav__title { font-size: 15px; font-weight: 600; flex: 1; text-align: center; color: #e2e8f0; }
.toggle-btn { padding: 4px 10px; border-radius: 6px; border: 1px solid rgba(255,255,255,0.1); background: transparent; color: #94a3b8; cursor: pointer; font-size: 12px; }
.toggle-btn.active { border-color: #6366f1; color: #6366f1; }
.create-btn { padding: 6px 14px; border-radius: 6px; border: none; background: #6366f1; color: #fff; font-weight: 600; cursor: pointer; font-size: 12px; }
.rapp-nav__btn { padding: 6px 14px; border-radius: 6px; border: none; background: #4f46e5; color: #fff; font-weight: 600; cursor: pointer; font-size: 12px; }
.weekdays { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; margin-bottom: 4px; }
.weekday { text-align: center; font-size: 11px; color: #666; padding: 4px; font-weight: 600; }
@ -119,11 +119,11 @@ class FolkCalendarView extends HTMLElement {
${this.error ? `<div style="color:#ef5350;text-align:center;padding:8px">${this.esc(this.error)}</div>` : ""}
<div class="header">
<button class="nav-btn" id="prev">\u2190</button>
<span class="header-title">${monthName} ${year}</span>
<div class="rapp-nav">
<button class="rapp-nav__back" id="prev">\u2190</button>
<span class="rapp-nav__title">${monthName} ${year}</span>
<button class="toggle-btn ${this.showLunar ? "active" : ""}" id="toggle-lunar">\u{1F319} Lunar</button>
<button class="nav-btn" id="next">\u2192</button>
<button class="rapp-nav__back" id="next">\u2192</button>
</div>
${this.sources.length > 0 ? `<div class="sources">

View File

@ -49,8 +49,8 @@ class FolkCartShop extends HTMLElement {
this.shadow.innerHTML = `
<style>
:host { display: block; padding: 1.5rem; }
.header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.5rem; }
.header h2 { margin: 0; color: #f1f5f9; font-size: 1.5rem; }
.rapp-nav { display: flex; gap: 8px; align-items: center; margin-bottom: 1rem; min-height: 36px; }
.rapp-nav__title { font-size: 15px; font-weight: 600; flex: 1; color: #e2e8f0; }
.tabs { display: flex; gap: 0.5rem; }
.tab { padding: 0.5rem 1rem; border-radius: 8px; border: 1px solid #334155; background: #1e293b; color: #94a3b8; cursor: pointer; font-size: 0.875rem; }
.tab:hover { border-color: #475569; color: #f1f5f9; }
@ -77,8 +77,8 @@ class FolkCartShop extends HTMLElement {
.loading { text-align: center; padding: 3rem; color: #94a3b8; }
</style>
<div class="header">
<h2>\u{1F6D2} Community Shop</h2>
<div class="rapp-nav">
<span class="rapp-nav__title">Shop</span>
<div class="tabs">
<button class="tab ${this.view === 'catalog' ? 'active' : ''}" data-view="catalog">\u{1F4E6} Catalog (${this.catalog.length})</button>
<button class="tab ${this.view === 'orders' ? 'active' : ''}" data-view="orders">\u{1F4CB} Orders (${this.orders.length})</button>

View File

@ -55,8 +55,8 @@ class FolkChoicesDashboard extends HTMLElement {
this.shadow.innerHTML = `
<style>
:host { display: block; padding: 1.5rem; }
.header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.5rem; }
.header h2 { margin: 0; color: #f1f5f9; font-size: 1.5rem; }
.rapp-nav { display: flex; gap: 8px; align-items: center; margin-bottom: 1rem; min-height: 36px; }
.rapp-nav__title { font-size: 15px; font-weight: 600; flex: 1; color: #e2e8f0; }
.create-btns { display: flex; gap: 0.5rem; }
.create-btn { padding: 0.5rem 1rem; border-radius: 8px; border: 1px solid #334155; background: #1e293b; color: #94a3b8; cursor: pointer; font-size: 0.875rem; text-decoration: none; }
.create-btn:hover { border-color: #6366f1; color: #f1f5f9; }
@ -75,8 +75,8 @@ class FolkChoicesDashboard extends HTMLElement {
.info { background: rgba(99,102,241,0.1); border: 1px solid rgba(99,102,241,0.2); border-radius: 8px; padding: 1rem; margin-bottom: 1.5rem; color: #a5b4fc; font-size: 0.8125rem; }
</style>
<div class="header">
<h2>\u2611 Choices</h2>
<div class="rapp-nav">
<span class="rapp-nav__title">Choices</span>
<div class="create-btns">
<a class="create-btn" href="/${this.space}/canvas" title="Open canvas to create choices">\u2795 New on Canvas</a>
</div>

View File

@ -37,9 +37,7 @@ class FolkAnalyticsView extends HTMLElement {
<style>
:host { display: block; min-height: 60vh; font-family: system-ui, sans-serif; color: #e2e8f0; }
.container { max-width: 800px; margin: 0 auto; }
.hero { text-align: center; margin-bottom: 2rem; }
.hero h2 { font-size: 1.75rem; font-weight: 700; margin-bottom: 0.5rem; }
.hero p { color: #94a3b8; max-width: 480px; margin: 0 auto; }
.desc { color: #94a3b8; font-size: 14px; line-height: 1.6; max-width: 600px; margin-bottom: 1.5rem; }
.stats-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1rem; margin-bottom: 2rem; }
.stat { text-align: center; background: rgba(15,23,42,0.5); border: 1px solid #1e293b; border-radius: 12px; padding: 1.25rem; }
.stat-value { font-size: 1.75rem; font-weight: 700; color: #22d3ee; }
@ -53,11 +51,11 @@ class FolkAnalyticsView extends HTMLElement {
.pillar h3 { font-size: 1rem; font-weight: 600; margin-bottom: 0.5rem; }
.pillar p { font-size: 0.85rem; color: #94a3b8; line-height: 1.5; }
.apps-section { margin-bottom: 2rem; }
.apps-title { text-align: center; font-size: 1.25rem; font-weight: 600; margin-bottom: 1rem; }
.apps-grid { display: flex; flex-wrap: wrap; gap: 0.5rem; justify-content: center; }
.apps-title { font-size: 1rem; font-weight: 600; margin-bottom: 1rem; }
.apps-grid { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.app-chip { padding: 0.35rem 0.75rem; background: rgba(15,23,42,0.5); border: 1px solid #1e293b; border-radius: 20px; font-size: 0.8rem; color: #94a3b8; }
.cta { text-align: center; padding: 2rem; border-top: 1px solid #1e293b; }
.cta a { display: inline-block; padding: 0.75rem 2rem; background: #22d3ee; color: #0f172a; border-radius: 8px; font-weight: 600; text-decoration: none; }
.cta { padding: 1.5rem 0; border-top: 1px solid #1e293b; }
.cta a { display: inline-block; padding: 0.6rem 1.5rem; background: #22d3ee; color: #0f172a; border-radius: 8px; font-weight: 600; text-decoration: none; font-size: 0.85rem; }
.cta a:hover { opacity: 0.85; }
@media (max-width: 768px) {
.stats-grid { grid-template-columns: repeat(2, 1fr); }
@ -65,10 +63,7 @@ class FolkAnalyticsView extends HTMLElement {
}
</style>
<div class="container">
<div class="hero">
<h2>Privacy-First Analytics</h2>
<p>Zero-knowledge, cookieless, self-hosted analytics for the r* ecosystem. Know how your tools are used without compromising anyone's privacy.</p>
</div>
<p class="desc">Zero-knowledge, cookieless, self-hosted analytics for the r* ecosystem. Know how your tools are used without compromising anyone's privacy.</p>
<div class="stats-grid">
<div class="stat">
@ -108,14 +103,14 @@ class FolkAnalyticsView extends HTMLElement {
</div>
<div class="apps-section">
<div class="apps-title">Tracking the r* Ecosystem</div>
<div class="apps-title">Tracked Apps</div>
<div class="apps-grid">
${(stats.apps || []).map((a: string) => `<span class="app-chip">${a}</span>`).join("")}
</div>
</div>
<div class="cta">
<a href="${stats.dashboardUrl}" target="_blank" rel="noopener">Open Dashboard</a>
<a href="${stats.dashboardUrl}" target="_blank" rel="noopener">Open Full Dashboard &rarr;</a>
</div>
</div>
`;

View File

@ -164,15 +164,19 @@ class FolkForumDashboard extends HTMLElement {
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }
.toolbar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
.rapp-nav { display: flex; gap: 8px; margin-bottom: 16px; align-items: center; min-height: 36px; }
.rapp-nav__back { padding: 4px 10px; border-radius: 6px; border: 1px solid rgba(255,255,255,0.1); background: transparent; color: #94a3b8; cursor: pointer; font-size: 13px; }
.rapp-nav__back:hover { color: #e2e8f0; border-color: rgba(255,255,255,0.2); }
.rapp-nav__title { font-size: 15px; font-weight: 600; flex: 1; color: #e2e8f0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rapp-nav__btn { padding: 6px 14px; border-radius: 6px; border: none; background: #4f46e5; color: #fff; font-weight: 600; cursor: pointer; font-size: 13px; }
.rapp-nav__btn:hover { background: #6366f1; }
button {
padding: 6px 14px; border-radius: 4px; border: 1px solid #555;
background: #2a4a7a; color: #e0e0e0; cursor: pointer; font-size: 13px;
padding: 6px 14px; border-radius: 6px; border: 1px solid rgba(255,255,255,0.1);
background: transparent; color: #94a3b8; cursor: pointer; font-size: 13px;
}
button:hover { background: #3a5a9a; }
button.danger { background: #7a2a2a; }
button.danger:hover { background: #9a3a3a; }
button.secondary { background: #333; }
button:hover { color: #e2e8f0; border-color: rgba(255,255,255,0.2); }
button.danger { background: rgba(239,68,68,0.15); border-color: rgba(239,68,68,0.3); color: #ef4444; }
button.danger:hover { background: rgba(239,68,68,0.25); }
input, select {
background: #2a2a3e; border: 1px solid #444; color: #e0e0e0;
@ -229,9 +233,9 @@ class FolkForumDashboard extends HTMLElement {
private renderList(): string {
return `
<div class="toolbar">
<h2 style="margin:0;font-size:18px">\uD83D\uDCAC Forum Instances</h2>
<button data-action="show-create">+ New Forum</button>
<div class="rapp-nav">
<span class="rapp-nav__title">Forum Instances</span>
<button class="rapp-nav__btn" data-action="show-create">+ New Forum</button>
</div>
${this.loading ? '<div class="loading">Loading...</div>' : ""}
@ -259,8 +263,9 @@ class FolkForumDashboard extends HTMLElement {
if (!inst) return "";
return `
<div class="toolbar">
<button class="secondary" data-action="back">\u2190 Back</button>
<div class="rapp-nav">
<button class="rapp-nav__back" data-action="back">\u2190 Forums</button>
<span class="rapp-nav__title">${this.esc(inst.name)}</span>
${inst.status !== "destroyed" ? `<button class="danger" data-action="destroy" data-id="${inst.id}">Destroy</button>` : ""}
</div>
@ -303,8 +308,9 @@ class FolkForumDashboard extends HTMLElement {
private renderCreate(): string {
return `
<div class="toolbar">
<button class="secondary" data-action="back">\u2190 Back</button>
<div class="rapp-nav">
<button class="rapp-nav__back" data-action="back">\u2190 Forums</button>
<span class="rapp-nav__title">Deploy New Forum</span>
</div>
<div class="detail-panel">

View File

@ -193,23 +193,23 @@ class FolkFundsApp extends HTMLElement {
return `
<div class="funds-landing">
<div class="funds-hero">
<h1 class="funds-hero__title">rFunds</h1>
<p class="funds-hero__subtitle">Token Bonding Flow Funnel</p>
<p class="funds-hero__desc">
Design transparent resource flows with sufficiency-based cascading.
Funnels fill to their threshold, then overflow routes surplus to the next layer &mdash;
ensuring every level has <em>enough</em> before abundance cascades forward.
</p>
<div class="funds-hero__actions">
<a href="${this.esc(demoUrl)}" class="funds-hero__cta">Try the Demo &rarr;</a>
<div class="rapp-nav">
<span class="rapp-nav__title">Flows</span>
<div class="rapp-nav__actions">
<a href="${this.esc(demoUrl)}" class="rapp-nav__btn rapp-nav__btn--secondary">Demo</a>
${authed
? `<button class="funds-hero__cta funds-hero__cta--secondary" data-action="create-flow">Create Flow</button>`
: `<span class="funds-hero__auth-hint">Sign in to create flows</span>`
? `<button class="rapp-nav__btn" data-action="create-flow">+ Create Flow</button>`
: `<span style="font-size:12px;color:#64748b">Sign in to create flows</span>`
}
</div>
</div>
<div class="funds-desc" style="color:#94a3b8;font-size:14px;line-height:1.6;max-width:600px;margin-bottom:24px">
Design transparent resource flows with sufficiency-based cascading.
Funnels fill to their threshold, then overflow routes surplus to the next layer &mdash;
ensuring every level has <em style="color:#fbbf24;font-style:normal;font-weight:600">enough</em> before abundance cascades forward.
</div>
<div class="funds-features">
<div class="funds-features__grid">
<div class="funds-features__card">
@ -316,10 +316,10 @@ class FolkFundsApp extends HTMLElement {
return `
<div class="funds-detail">
<div class="funds-detail__header">
<a href="${this.esc(backUrl)}" class="funds-detail__back">&larr; All Flows</a>
<h1 class="funds-detail__title">${this.esc(this.flowName || "Flow Detail")}</h1>
${this.isDemo ? '<span class="funds-detail__badge">Demo</span>' : ""}
<div class="rapp-nav">
<a href="${this.esc(backUrl)}" class="rapp-nav__back">&larr; Flows</a>
<span class="rapp-nav__title">${this.esc(this.flowName || "Flow Detail")}</span>
${this.isDemo ? '<span class="rapp-nav__badge">Demo</span>' : ""}
</div>
<div class="funds-tabs">

View File

@ -1,9 +1,4 @@
/* ── Funds module theme ───────────────────────────────── */
body[data-theme="light"] main {
background: #0f172a;
min-height: calc(100vh - 56px);
padding: 0;
}
/* ── Shared utility classes ──────────────────────────── */
.funds-loading { text-align: center; color: #64748b; padding: 48px 16px; font-size: 14px; }
@ -12,34 +7,6 @@ body[data-theme="light"] main {
/* ── Landing page ────────────────────────────────────── */
.funds-landing { max-width: 960px; margin: 0 auto; padding: 24px 20px 64px; }
.funds-hero {
text-align: center;
padding: 56px 20px 48px;
border-bottom: 1px solid #1e293b;
margin-bottom: 48px;
}
.funds-hero__title {
font-size: 42px; font-weight: 800; margin: 0 0 8px;
background: linear-gradient(135deg, #0ea5e9, #6366f1, #ec4899);
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
background-clip: text;
}
.funds-hero__subtitle { font-size: 18px; color: #94a3b8; margin: 0 0 16px; font-weight: 500; }
.funds-hero__desc { font-size: 15px; color: #64748b; line-height: 1.7; max-width: 560px; margin: 0 auto 28px; }
.funds-hero__desc em { color: #fbbf24; font-style: normal; font-weight: 600; }
.funds-hero__actions { display: flex; align-items: center; justify-content: center; gap: 12px; flex-wrap: wrap; }
.funds-hero__cta {
display: inline-block; padding: 10px 24px; border-radius: 8px;
background: #4f46e5; color: #fff; text-decoration: none; font-weight: 600; font-size: 14px;
border: none; cursor: pointer; transition: background 0.2s;
}
.funds-hero__cta:hover { background: #6366f1; }
.funds-hero__cta--secondary {
background: transparent; border: 1px solid #4f46e5; color: #a5b4fc;
}
.funds-hero__cta--secondary:hover { background: rgba(79,70,229,0.15); }
.funds-hero__auth-hint { font-size: 13px; color: #64748b; }
/* Features grid */
.funds-features { margin-bottom: 48px; }
.funds-features__grid {
@ -106,16 +73,6 @@ body[data-theme="light"] main {
/* ── Detail view ─────────────────────────────────────── */
.funds-detail { max-width: 1100px; margin: 0 auto; padding: 16px 20px 64px; }
.funds-detail__header { display: flex; align-items: center; gap: 12px; margin-bottom: 16px; flex-wrap: wrap; }
.funds-detail__back {
color: #64748b; text-decoration: none; font-size: 13px; padding: 4px 0;
}
.funds-detail__back:hover { color: #e2e8f0; }
.funds-detail__title { font-size: 22px; font-weight: 700; color: #e2e8f0; margin: 0; flex: 1; }
.funds-detail__badge {
font-size: 11px; font-weight: 600; color: #fbbf24; background: rgba(251,191,36,0.15);
border: 1px solid rgba(251,191,36,0.3); border-radius: 4px; padding: 2px 8px;
}
/* ── Tabs ────────────────────────────────────────────── */
.funds-tabs {

View File

@ -90,11 +90,11 @@ class FolkInboxClient extends HTMLElement {
<style>
:host { display: block; min-height: 60vh; font-family: system-ui, sans-serif; color: #e2e8f0; }
.container { max-width: 1000px; margin: 0 auto; }
.nav { display: flex; gap: 0.5rem; margin-bottom: 1.5rem; align-items: center; }
.rapp-nav { display: flex; gap: 0.5rem; margin-bottom: 1rem; align-items: center; min-height: 36px; }
.rapp-nav__back { padding: 4px 10px; border-radius: 6px; border: 1px solid rgba(255,255,255,0.1); background: transparent; color: #94a3b8; cursor: pointer; font-size: 0.8rem; }
.rapp-nav__back:hover { color: #e2e8f0; border-color: rgba(255,255,255,0.2); }
.nav-btn { padding: 0.4rem 1rem; border-radius: 8px; border: 1px solid #334155; background: transparent; color: #94a3b8; cursor: pointer; font-size: 0.8rem; }
.nav-btn.active { background: #6366f1; color: white; border-color: #6366f1; }
.back-btn { padding: 0.4rem 0.75rem; border-radius: 8px; border: 1px solid #334155; background: transparent; color: #94a3b8; cursor: pointer; font-size: 0.8rem; margin-right: 0.5rem; }
.back-btn:hover { background: rgba(51,65,85,0.5); }
.card { background: rgba(15,23,42,0.5); border: 1px solid #1e293b; border-radius: 12px; padding: 1.25rem; margin-bottom: 0.75rem; cursor: pointer; transition: border-color 0.2s; }
.card:hover { border-color: #6366f1; }
.card-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 0.5rem; }
@ -154,8 +154,8 @@ class FolkInboxClient extends HTMLElement {
items.unshift({ id: "threads", label: this.currentMailbox.name });
}
return `
<div class="nav">
${this.view !== "mailboxes" ? `<button class="back-btn" data-action="back">&larr;</button>` : ""}
<div class="rapp-nav">
${this.view !== "mailboxes" ? `<button class="rapp-nav__back" data-action="back">&larr;</button>` : ""}
${items.map((i) => `<button class="nav-btn ${this.view === i.id ? "active" : ""}" data-nav="${i.id}">${i.label}</button>`).join("")}
</div>
`;

View File

@ -83,13 +83,10 @@ class FolkMapViewer extends HTMLElement {
:host { display: block; font-family: system-ui, -apple-system, sans-serif; color: #e0e0e0; }
* { box-sizing: border-box; }
.header { display: flex; gap: 8px; margin-bottom: 20px; align-items: center; }
.nav-btn {
padding: 6px 14px; border-radius: 6px; border: 1px solid #444;
background: #1e1e2e; color: #ccc; cursor: pointer; font-size: 13px;
}
.nav-btn:hover { border-color: #666; }
.header-title { font-size: 18px; font-weight: 600; margin-left: 8px; flex: 1; }
.rapp-nav { display: flex; gap: 8px; margin-bottom: 16px; align-items: center; min-height: 36px; }
.rapp-nav__back { padding: 4px 10px; border-radius: 6px; border: 1px solid rgba(255,255,255,0.1); background: transparent; color: #94a3b8; cursor: pointer; font-size: 13px; }
.rapp-nav__back:hover { color: #e2e8f0; border-color: rgba(255,255,255,0.2); }
.rapp-nav__title { font-size: 15px; font-weight: 600; flex: 1; color: #e2e8f0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.status-dot {
width: 8px; height: 8px; border-radius: 50%; display: inline-block;
@ -97,11 +94,8 @@ class FolkMapViewer extends HTMLElement {
.status-connected { background: #22c55e; }
.status-disconnected { background: #ef4444; }
.create-btn {
padding: 10px 20px; border-radius: 8px; border: none;
background: #6366f1; color: #fff; font-weight: 600; cursor: pointer; font-size: 14px;
}
.create-btn:hover { background: #4f46e5; }
.rapp-nav__btn { padding: 6px 14px; border-radius: 6px; border: none; background: #4f46e5; color: #fff; font-weight: 600; cursor: pointer; font-size: 13px; }
.rapp-nav__btn:hover { background: #6366f1; }
.room-card {
background: #1e1e2e; border: 1px solid #333; border-radius: 10px;
@ -162,11 +156,11 @@ class FolkMapViewer extends HTMLElement {
private renderLobby(): string {
return `
<div class="header">
<span class="header-title">Map Rooms</span>
<div class="rapp-nav">
<span class="rapp-nav__title">Map Rooms</span>
<span class="status-dot ${this.syncStatus === "connected" ? "status-connected" : "status-disconnected"}"></span>
<span style="font-size:12px;color:#888;margin-right:12px">${this.syncStatus === "connected" ? "Sync online" : "Sync offline"}</span>
<button class="create-btn" id="create-room">+ New Room</button>
<button class="rapp-nav__btn" id="create-room">+ New Room</button>
</div>
${this.rooms.length > 0 ? this.rooms.map((r) => `
@ -186,9 +180,9 @@ class FolkMapViewer extends HTMLElement {
private renderMap(): string {
const shareUrl = `${window.location.origin}/${this.space}/maps/${this.room}`;
return `
<div class="header">
<button class="nav-btn" data-back="lobby">Back</button>
<span class="header-title">\u{1F5FA} ${this.esc(this.room)}</span>
<div class="rapp-nav">
<button class="rapp-nav__back" data-back="lobby">\u2190 Rooms</button>
<span class="rapp-nav__title">\u{1F5FA} ${this.esc(this.room)}</span>
<span class="status-dot ${this.syncStatus === "connected" ? "status-connected" : "status-disconnected"}"></span>
</div>

View File

@ -50,8 +50,8 @@ class FolkGraphViewer extends HTMLElement {
:host { display: block; font-family: system-ui, -apple-system, sans-serif; color: #e0e0e0; }
* { box-sizing: border-box; }
.header { display: flex; gap: 8px; margin-bottom: 16px; align-items: center; }
.header-title { font-size: 18px; font-weight: 600; flex: 1; }
.rapp-nav { display: flex; gap: 8px; margin-bottom: 16px; align-items: center; min-height: 36px; }
.rapp-nav__title { font-size: 15px; font-weight: 600; flex: 1; color: #e2e8f0; }
.toolbar { display: flex; gap: 8px; margin-bottom: 16px; align-items: center; flex-wrap: wrap; }
.search-input {
@ -99,8 +99,8 @@ class FolkGraphViewer extends HTMLElement {
${this.error ? `<div style="color:#ef5350;text-align:center;padding:8px">${this.esc(this.error)}</div>` : ""}
<div class="header">
<span class="header-title">\u{1F310} Network Graph</span>
<div class="rapp-nav">
<span class="rapp-nav__title">Network Graph</span>
</div>
<div class="toolbar">

View File

@ -457,18 +457,12 @@ class FolkNotesApp extends HTMLElement {
:host { display: block; font-family: system-ui, -apple-system, sans-serif; color: #e0e0e0; }
* { box-sizing: border-box; }
.header { display: flex; gap: 8px; margin-bottom: 20px; align-items: center; }
.nav-btn {
padding: 6px 14px; border-radius: 6px; border: 1px solid #444;
background: #1e1e2e; color: #ccc; cursor: pointer; font-size: 13px;
}
.nav-btn:hover { border-color: #666; }
.header-title { font-size: 18px; font-weight: 600; margin-left: 8px; flex: 1; }
.create-btn {
padding: 8px 16px; border-radius: 8px; border: none;
background: #6366f1; color: #fff; font-weight: 600; cursor: pointer; font-size: 13px;
}
.create-btn:hover { background: #4f46e5; }
.rapp-nav { display: flex; gap: 8px; margin-bottom: 16px; align-items: center; min-height: 36px; }
.rapp-nav__back { padding: 4px 10px; border-radius: 6px; border: 1px solid rgba(255,255,255,0.1); background: transparent; color: #94a3b8; cursor: pointer; font-size: 13px; }
.rapp-nav__back:hover { color: #e2e8f0; border-color: rgba(255,255,255,0.2); }
.rapp-nav__title { font-size: 15px; font-weight: 600; flex: 1; color: #e2e8f0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rapp-nav__btn { padding: 6px 14px; border-radius: 6px; border: none; background: #4f46e5; color: #fff; font-weight: 600; cursor: pointer; font-size: 13px; }
.rapp-nav__btn:hover { background: #6366f1; }
.search-bar {
width: 100%; padding: 10px 14px; border-radius: 8px;
@ -543,9 +537,9 @@ class FolkNotesApp extends HTMLElement {
private renderNotebooks(): string {
return `
<div class="header">
<span class="header-title">Notebooks</span>
<button class="create-btn" id="create-notebook">+ New Notebook</button>
<div class="rapp-nav">
<span class="rapp-nav__title">Notebooks</span>
<button class="rapp-nav__btn" id="create-notebook">+ New Notebook</button>
</div>
<input class="search-bar" type="text" placeholder="Search notes..." id="search-input" value="${this.esc(this.searchQuery)}">
@ -578,10 +572,10 @@ class FolkNotesApp extends HTMLElement {
? `<span class="sync-badge ${this.syncConnected ? "connected" : "disconnected"}" title="${this.syncConnected ? "Live sync" : "Reconnecting..."}"></span>`
: "";
return `
<div class="header">
<button class="nav-btn" data-back="notebooks">Back</button>
<span class="header-title" style="color:${nb.cover_color}">${this.esc(nb.title)}${syncBadge}</span>
<button class="create-btn" id="create-note">+ New Note</button>
<div class="rapp-nav">
<button class="rapp-nav__back" data-back="notebooks">\u2190 Notebooks</button>
<span class="rapp-nav__title" style="color:${nb.cover_color}">${this.esc(nb.title)}${syncBadge}</span>
<button class="rapp-nav__btn" id="create-note">+ New Note</button>
</div>
${nb.notes && nb.notes.length > 0
? nb.notes.map((n) => this.renderNoteItem(n)).join("")
@ -611,11 +605,11 @@ class FolkNotesApp extends HTMLElement {
const n = this.selectedNote!;
const isAutomerge = !!(this.doc?.items?.[n.id]);
return `
<div class="header">
<button class="nav-btn" data-back="${this.selectedNotebook ? "notebook" : "notebooks"}">Back</button>
<div class="rapp-nav">
<button class="rapp-nav__back" data-back="${this.selectedNotebook ? "notebook" : "notebooks"}">\u2190 ${this.selectedNotebook ? this.esc(this.selectedNotebook.title) : "Notebooks"}</button>
${isAutomerge
? `<input class="editable-title" id="note-title-input" value="${this.esc(n.title)}" placeholder="Note title...">`
: `<span class="header-title">${this.getNoteIcon(n.type)} ${this.esc(n.title)}</span>`
: `<span class="rapp-nav__title">${this.getNoteIcon(n.type)} ${this.esc(n.title)}</span>`
}
</div>
<div class="note-content" ${isAutomerge ? 'contenteditable="true" id="note-content-editable"' : ""}>${n.content || '<em style="color:#666">Empty note</em>'}</div>

View File

@ -70,8 +70,8 @@ class FolkProviderDirectory extends HTMLElement {
this.shadow.innerHTML = `
<style>
:host { display: block; padding: 1.5rem; }
.header { display: flex; flex-wrap: wrap; gap: 1rem; align-items: center; margin-bottom: 1.5rem; }
.header h2 { margin: 0; font-size: 1.5rem; color: #f1f5f9; flex: 1; }
.rapp-nav { display: flex; flex-wrap: wrap; gap: 0.75rem; align-items: center; margin-bottom: 1rem; min-height: 36px; }
.rapp-nav__title { font-size: 15px; font-weight: 600; color: #e2e8f0; flex: 1; }
.search { padding: 0.5rem 0.75rem; border-radius: 8px; border: 1px solid #334155; background: #1e293b; color: #f1f5f9; font-size: 0.875rem; width: 240px; }
.search:focus { outline: none; border-color: #6366f1; }
.locate-btn { padding: 0.5rem 0.75rem; border-radius: 8px; border: 1px solid #334155; background: #1e293b; color: #94a3b8; cursor: pointer; font-size: 0.75rem; }
@ -99,8 +99,8 @@ class FolkProviderDirectory extends HTMLElement {
.empty { text-align: center; padding: 3rem; color: #64748b; }
</style>
<div class="header">
<h2>\u{1F3ED} Provider Directory</h2>
<div class="rapp-nav">
<span class="rapp-nav__title">Provider Directory</span>
<input class="search" type="text" placeholder="Search providers..." value="${this.searchQuery}">
<button class="locate-btn ${this.userLat ? 'active' : ''}">\u{1F4CD} ${this.userLat ? 'Nearby' : 'Use location'}</button>
</div>

View File

@ -409,9 +409,9 @@ class FolkRoutePlanner extends HTMLElement {
<style>
:host { display: block; font-family: -apple-system, system-ui, sans-serif; color: #e0e0e0; }
.container { background: #1a1a2e; border-radius: 12px; overflow: hidden; }
.header { padding: 16px 20px; border-bottom: 1px solid rgba(255,255,255,0.08); }
.header h2 { margin: 0 0 4px 0; font-size: 18px; color: #fff; }
.header p { margin: 0; font-size: 13px; opacity: 0.6; }
.rapp-nav { padding: 16px 20px; border-bottom: 1px solid rgba(255,255,255,0.08); }
.rapp-nav__title { font-size: 15px; font-weight: 600; color: #e2e8f0; display: block; margin-bottom: 4px; }
.rapp-nav__desc { font-size: 13px; color: #94a3b8; }
.input-panel { padding: 16px 20px; display: flex; flex-wrap: wrap; gap: 12px; align-items: flex-end; border-bottom: 1px solid rgba(255,255,255,0.08); }
.route-group { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
@ -459,9 +459,9 @@ class FolkRoutePlanner extends HTMLElement {
.math-note { padding: 12px 20px; border-top: 1px solid rgba(255,255,255,0.05); font-size: 11px; opacity: 0.4; font-family: monospace; }
</style>
<div class="container">
<div class="header">
<h2>rTrips Route Planner</h2>
<p>Plan paths between destinations. Fit conic arcs and find where routes intersect.</p>
<div class="rapp-nav">
<span class="rapp-nav__title">Route Planner</span>
<span class="rapp-nav__desc">Plan paths between destinations. Fit conic arcs and find where routes intersect.</span>
</div>
<div class="input-panel">

View File

@ -69,11 +69,12 @@ class FolkTripsPlanner extends HTMLElement {
:host { display: block; font-family: system-ui, -apple-system, sans-serif; color: #e0e0e0; }
* { box-sizing: border-box; }
.header { display: flex; gap: 8px; margin-bottom: 16px; align-items: center; }
.nav-btn { padding: 6px 14px; border-radius: 6px; border: 1px solid #444; background: #1e1e2e; color: #ccc; cursor: pointer; font-size: 13px; }
.header-title { font-size: 18px; font-weight: 600; margin-left: 8px; flex: 1; }
.create-btn { padding: 8px 16px; border-radius: 8px; border: none; background: #14b8a6; color: #fff; font-weight: 600; cursor: pointer; font-size: 13px; }
.create-btn:hover { background: #0d9488; }
.rapp-nav { display: flex; gap: 8px; margin-bottom: 16px; align-items: center; min-height: 36px; }
.rapp-nav__back { padding: 4px 10px; border-radius: 6px; border: 1px solid rgba(255,255,255,0.1); background: transparent; color: #94a3b8; cursor: pointer; font-size: 13px; }
.rapp-nav__back:hover { color: #e2e8f0; border-color: rgba(255,255,255,0.2); }
.rapp-nav__title { font-size: 15px; font-weight: 600; flex: 1; color: #e2e8f0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rapp-nav__btn { padding: 6px 14px; border-radius: 6px; border: none; background: #14b8a6; color: #fff; font-weight: 600; cursor: pointer; font-size: 13px; }
.rapp-nav__btn:hover { background: #0d9488; }
.trip-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 12px; }
.trip-card { background: #1e1e2e; border: 1px solid #333; border-radius: 10px; padding: 16px; cursor: pointer; transition: border-color 0.2s; }
@ -114,9 +115,9 @@ class FolkTripsPlanner extends HTMLElement {
private renderList(): string {
return `
<div class="header">
<span class="header-title">\u2708\uFE0F My Trips</span>
<button class="create-btn" id="create-trip">+ Plan a Trip</button>
<div class="rapp-nav">
<span class="rapp-nav__title">My Trips</span>
<button class="rapp-nav__btn" id="create-trip">+ Plan a Trip</button>
</div>
${this.trips.length > 0 ? `<div class="trip-grid">
${this.trips.map(t => `
@ -141,9 +142,9 @@ class FolkTripsPlanner extends HTMLElement {
if (!this.trip) return '<div class="empty">Loading...</div>';
const t = this.trip;
return `
<div class="header">
<button class="nav-btn" data-back="list">\u2190 Back</button>
<span class="header-title">\u2708\uFE0F ${this.esc(t.title)}</span>
<div class="rapp-nav">
<button class="rapp-nav__back" data-back="list">\u2190 Trips</button>
<span class="rapp-nav__title">${this.esc(t.title)}</span>
<span class="trip-status status-${t.status || "PLANNING"}">${t.status || "PLANNING"}</span>
</div>
<div class="tabs">

View File

@ -145,14 +145,10 @@ class FolkVoteDashboard extends HTMLElement {
:host { display: block; font-family: system-ui, -apple-system, sans-serif; color: #e0e0e0; }
* { box-sizing: border-box; }
.nav { display: flex; gap: 8px; margin-bottom: 20px; align-items: center; }
.nav-btn {
padding: 6px 14px; border-radius: 6px; border: 1px solid #444;
background: #1e1e2e; color: #ccc; cursor: pointer; font-size: 13px;
}
.nav-btn:hover { border-color: #666; }
.nav-btn.active { border-color: #6366f1; color: #a5b4fc; }
.nav-title { font-size: 18px; font-weight: 600; margin-left: 8px; }
.rapp-nav { display: flex; gap: 8px; margin-bottom: 16px; align-items: center; min-height: 36px; }
.rapp-nav__back { padding: 4px 10px; border-radius: 6px; border: 1px solid rgba(255,255,255,0.1); background: transparent; color: #94a3b8; cursor: pointer; font-size: 13px; }
.rapp-nav__back:hover { color: #e2e8f0; border-color: rgba(255,255,255,0.2); }
.rapp-nav__title { font-size: 15px; font-weight: 600; flex: 1; color: #e2e8f0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.card {
background: #1e1e2e; border: 1px solid #333; border-radius: 10px;
@ -213,8 +209,8 @@ class FolkVoteDashboard extends HTMLElement {
private renderSpaces(): string {
return `
<div class="nav">
<span class="nav-title">Voting Spaces</span>
<div class="rapp-nav">
<span class="rapp-nav__title">Voting Spaces</span>
</div>
${this.spaces.length === 0 ? '<div class="empty">No voting spaces yet. Create one to get started.</div>' : ""}
${this.spaces.map((s) => `
@ -234,9 +230,9 @@ class FolkVoteDashboard extends HTMLElement {
private renderProposals(): string {
const s = this.selectedSpace!;
return `
<div class="nav">
<button class="nav-btn" data-back="spaces">Back</button>
<span class="nav-title">${this.esc(s.name)} Proposals</span>
<div class="rapp-nav">
<button class="rapp-nav__back" data-back="spaces">\u2190 Spaces</button>
<span class="rapp-nav__title">${this.esc(s.name)} Proposals</span>
</div>
${this.proposals.length === 0 ? '<div class="empty">No proposals yet.</div>' : ""}
${this.proposals.map((p) => `
@ -267,9 +263,9 @@ class FolkVoteDashboard extends HTMLElement {
private renderProposal(): string {
const p = this.selectedProposal!;
return `
<div class="nav">
<button class="nav-btn" data-back="proposals">Back</button>
<span class="nav-title">${this.esc(p.title)}</span>
<div class="rapp-nav">
<button class="rapp-nav__back" data-back="proposals">\u2190 Proposals</button>
<span class="rapp-nav__title">${this.esc(p.title)}</span>
<span class="badge" style="background:${this.getStatusColor(p.status)}20;color:${this.getStatusColor(p.status)};margin-left:8px">${p.status}</span>
</div>
<div class="card" style="cursor:default">

View File

@ -110,12 +110,12 @@ class FolkWorkBoard extends HTMLElement {
:host { display: block; font-family: system-ui, -apple-system, sans-serif; color: #e0e0e0; }
* { box-sizing: border-box; }
.header { display: flex; gap: 8px; margin-bottom: 16px; align-items: center; }
.nav-btn { padding: 6px 14px; border-radius: 6px; border: 1px solid #444; background: #1e1e2e; color: #ccc; cursor: pointer; font-size: 13px; }
.nav-btn:hover { border-color: #666; }
.header-title { font-size: 18px; font-weight: 600; margin-left: 8px; flex: 1; }
.create-btn { padding: 8px 16px; border-radius: 8px; border: none; background: #6366f1; color: #fff; font-weight: 600; cursor: pointer; font-size: 13px; }
.create-btn:hover { background: #4f46e5; }
.rapp-nav { display: flex; gap: 8px; margin-bottom: 16px; align-items: center; min-height: 36px; }
.rapp-nav__back { padding: 4px 10px; border-radius: 6px; border: 1px solid rgba(255,255,255,0.1); background: transparent; color: #94a3b8; cursor: pointer; font-size: 13px; text-decoration: none; }
.rapp-nav__back:hover { color: #e2e8f0; border-color: rgba(255,255,255,0.2); }
.rapp-nav__title { font-size: 15px; font-weight: 600; flex: 1; color: #e2e8f0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rapp-nav__btn { padding: 6px 14px; border-radius: 6px; border: none; background: #4f46e5; color: #fff; font-weight: 600; cursor: pointer; font-size: 13px; }
.rapp-nav__btn:hover { background: #6366f1; }
.workspace-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 12px; }
.workspace-card {
@ -160,9 +160,9 @@ class FolkWorkBoard extends HTMLElement {
private renderList(): string {
return `
<div class="header">
<span class="header-title">Workspaces</span>
<button class="create-btn" id="create-ws">+ New Workspace</button>
<div class="rapp-nav">
<span class="rapp-nav__title">Workspaces</span>
<button class="rapp-nav__btn" id="create-ws">+ New Workspace</button>
</div>
${this.workspaces.length > 0 ? `<div class="workspace-grid">
${this.workspaces.map(ws => `
@ -180,10 +180,10 @@ class FolkWorkBoard extends HTMLElement {
private renderBoard(): string {
return `
<div class="header">
<button class="nav-btn" data-back="list">\u2190 Back</button>
<span class="header-title">${this.esc(this.workspaceSlug)}</span>
<button class="create-btn" id="create-task">+ New Task</button>
<div class="rapp-nav">
<button class="rapp-nav__back" data-back="list">\u2190 Workspaces</button>
<span class="rapp-nav__title">${this.esc(this.workspaceSlug)}</span>
<button class="rapp-nav__btn" id="create-task">+ New Task</button>
</div>
<div class="board">
${this.statuses.map(status => {

View File

@ -112,6 +112,89 @@ body {
gap: 0;
}
/* ── Shared in-app navigation bar (used by module components) ── */
.rapp-nav {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 16px;
padding: 0;
min-height: 36px;
}
.rapp-nav__back {
padding: 4px 10px;
border-radius: 6px;
border: 1px solid rgba(255,255,255,0.1);
background: transparent;
color: #94a3b8;
cursor: pointer;
font-size: 13px;
text-decoration: none;
transition: color 0.15s, border-color 0.15s;
}
.rapp-nav__back:hover {
color: #e2e8f0;
border-color: rgba(255,255,255,0.2);
}
.rapp-nav__title {
font-size: 15px;
font-weight: 600;
color: #e2e8f0;
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.rapp-nav__actions {
display: flex;
align-items: center;
gap: 8px;
flex-shrink: 0;
}
.rapp-nav__btn {
padding: 6px 14px;
border-radius: 6px;
border: none;
background: #4f46e5;
color: #fff;
font-weight: 600;
cursor: pointer;
font-size: 13px;
transition: background 0.15s;
}
.rapp-nav__btn:hover {
background: #6366f1;
}
.rapp-nav__btn--secondary {
background: transparent;
border: 1px solid rgba(255,255,255,0.15);
color: #94a3b8;
}
.rapp-nav__btn--secondary:hover {
border-color: rgba(255,255,255,0.3);
color: #e2e8f0;
}
.rapp-nav__badge {
font-size: 11px;
font-weight: 600;
color: #fbbf24;
background: rgba(251,191,36,0.15);
border: 1px solid rgba(251,191,36,0.3);
border-radius: 4px;
padding: 2px 8px;
}
/* ── Mobile adjustments ── */
@media (max-width: 640px) {