feat: strip demo pages to show just the interactive component
Demo pages now render the same clean shell as regular spaces — just the <folk-*> component full-page, no marketing wrapper (hero, feature cards, CTA). Descriptions belong on landing pages, not demos. - Remove demo branch from 7 module route handlers (rcal, rcart, rfunds, rnotes, rtrips, rtube, rvote) - Delete 7 demo.ts files (~1200 lines of dead markup) - Remove renderDemoShell() and DEMO_PAGE_CSS from server/shell.ts - Remove demoPage field from RSpaceModule interface - Rename top rApp dropdown item from "rSpace" to "rStack" Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
6bd23a6778
commit
3808b51a64
|
|
@ -1,165 +0,0 @@
|
|||
/**
|
||||
* rCal demo page — server-rendered HTML body.
|
||||
*
|
||||
* Embeds the full <folk-calendar-view space="demo"> component for
|
||||
* real interactivity (month/week/day views, navigation, lunar overlay,
|
||||
* source filtering, event modals, keyboard shortcuts) plus showcase
|
||||
* sections explaining the rCal vision.
|
||||
*/
|
||||
|
||||
const FEATURES = [
|
||||
{
|
||||
icon: "\u{1F50D}",
|
||||
title: "Temporal Zoom",
|
||||
desc: "Navigate seamlessly from geological eras down to individual minutes. The calendar adapts its grid density and label fidelity at every level.",
|
||||
},
|
||||
{
|
||||
icon: "\u{1F30D}",
|
||||
title: "Spatial Context",
|
||||
desc: "Events are location-aware. Zoom the map and the calendar filters to show only events within the visible region.",
|
||||
},
|
||||
{
|
||||
icon: "\u{1F319}",
|
||||
title: "Lunar Cycles",
|
||||
desc: "Overlay moon phases, tidal patterns, and seasonal markers. Useful for agriculture, ceremony, and natural rhythm tracking.",
|
||||
},
|
||||
{
|
||||
icon: "\u{1F4C5}",
|
||||
title: "Multi-Calendar",
|
||||
desc: "Layer Gregorian, Islamic, Hebrew, Chinese, and custom community calendars. Cross-reference events across time systems.",
|
||||
},
|
||||
];
|
||||
|
||||
const INTEGRATIONS = [
|
||||
{ icon: "\u{1F5FA}", name: "rTrips", desc: "Travel itineraries surface as calendar events with departure/arrival times and locations." },
|
||||
{ icon: "\u{1F30D}", name: "rMaps", desc: "Events appear on the map. Zoom the map and the calendar filters to show only visible events." },
|
||||
{ icon: "\u{1F465}", name: "rNetwork", desc: "See availability across your network. Coordinate meetings without back-and-forth." },
|
||||
{ icon: "\u{1F4DD}", name: "rNotes", desc: "Link notes to calendar events. Meeting agendas, daily journals, and retrospective logs." },
|
||||
{ icon: "\u{1F4B0}", name: "rFunds", desc: "Budget reviews, treasury flows, and governance votes appear on the calendar timeline." },
|
||||
{ icon: "\u2615\uFE0F", name: "rSpace", desc: "Each space has its own calendar layer. Nest calendars across spaces for cross-community coordination." },
|
||||
];
|
||||
|
||||
const ZOOM_LEVELS = [
|
||||
"Era", "Century", "Decade", "Year", "Quarter",
|
||||
"Month", "Week", "Day", "Hour", "Minute",
|
||||
];
|
||||
|
||||
export function renderDemo(): string {
|
||||
return `
|
||||
<div class="rd-root" style="--rd-accent-from:#6366f1; --rd-accent-to:#a78bfa;">
|
||||
|
||||
<!-- ── Hero ── -->
|
||||
<section class="rd-hero">
|
||||
<div style="display:inline-block;padding:0.375rem 1rem;background:rgba(99,102,241,0.1);border:1px solid rgba(99,102,241,0.2);border-radius:9999px;font-size:0.875rem;color:#a5b4fc;font-weight:500;margin-bottom:1.5rem;">
|
||||
Multi-Dimensional Calendar
|
||||
</div>
|
||||
<h1>rCal Demo</h1>
|
||||
<p class="rd-subtitle">Temporal coordination with lunar cycles, spatial context, and multi-scale zoom</p>
|
||||
<div class="rd-meta">
|
||||
<span>\u{1F50D} Temporal Zoom</span>
|
||||
<span style="color:#475569">|</span>
|
||||
<span>\u{1F30D} Spatial Context</span>
|
||||
<span style="color:#475569">|</span>
|
||||
<span>\u{1F319} Lunar Cycles</span>
|
||||
<span style="color:#475569">|</span>
|
||||
<span>\u{1F4C5} Multi-Calendar</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ── Interactive Calendar ── -->
|
||||
<section class="rd-section rd-section--narrow">
|
||||
<div class="rd-card" style="padding:0;overflow:hidden;">
|
||||
<folk-calendar-view space="demo"></folk-calendar-view>
|
||||
</div>
|
||||
<div style="text-align:center;padding:0.75rem 0;font-size:0.8rem;color:#64748b;">
|
||||
<kbd style="background:#1e293b;padding:2px 6px;border-radius:4px;border:1px solid #334155;font-size:0.75rem;">1</kbd>
|
||||
<kbd style="background:#1e293b;padding:2px 6px;border-radius:4px;border:1px solid #334155;font-size:0.75rem;">2</kbd>
|
||||
<kbd style="background:#1e293b;padding:2px 6px;border-radius:4px;border:1px solid #334155;font-size:0.75rem;">3</kbd>
|
||||
Day / Week / Month \u00B7
|
||||
<kbd style="background:#1e293b;padding:2px 6px;border-radius:4px;border:1px solid #334155;font-size:0.75rem;">\u2190</kbd>
|
||||
<kbd style="background:#1e293b;padding:2px 6px;border-radius:4px;border:1px solid #334155;font-size:0.75rem;">\u2192</kbd>
|
||||
Navigate \u00B7
|
||||
<kbd style="background:#1e293b;padding:2px 6px;border-radius:4px;border:1px solid #334155;font-size:0.75rem;">L</kbd>
|
||||
Lunar \u00B7
|
||||
<kbd style="background:#1e293b;padding:2px 6px;border-radius:4px;border:1px solid #334155;font-size:0.75rem;">T</kbd>
|
||||
Today
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ── Temporal Zoom Showcase ── -->
|
||||
<section class="rd-section rd-section--narrow">
|
||||
<div class="rd-card" style="padding:1.5rem;">
|
||||
<h2 style="font-size:1.125rem;font-weight:600;color:#f1f5f9;margin:0 0 0.75rem;display:flex;align-items:center;gap:0.5rem;">
|
||||
\u{1F50D} Temporal Zoom
|
||||
</h2>
|
||||
<p style="font-size:0.875rem;color:#94a3b8;margin:0 0 1rem;line-height:1.5;">
|
||||
Navigate across 10 temporal granularities. The calendar adapts its grid at each level —
|
||||
from geological eras to individual minutes.
|
||||
</p>
|
||||
<div style="display:flex;flex-wrap:wrap;gap:0.5rem;">
|
||||
${ZOOM_LEVELS.map(
|
||||
(level) => {
|
||||
const isActive = level === "Month";
|
||||
return `<div style="
|
||||
padding:0.5rem 1rem;
|
||||
border-radius:0.5rem;
|
||||
font-size:0.8rem;
|
||||
font-weight:500;
|
||||
border:1px solid ${isActive ? "rgba(99,102,241,0.4)" : "rgba(51,65,85,0.4)"};
|
||||
background:${isActive ? "rgba(99,102,241,0.15)" : "rgba(30,41,59,0.5)"};
|
||||
color:${isActive ? "#818cf8" : "#64748b"};
|
||||
${isActive ? "box-shadow:0 0 12px rgba(99,102,241,0.2);" : ""}
|
||||
">${level}${isActive ? " \u25C0" : ""}</div>`;
|
||||
},
|
||||
).join("\n ")}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ── Core Concepts ── -->
|
||||
<section class="rd-section">
|
||||
<div class="rd-grid rd-grid--2">
|
||||
${FEATURES.map(
|
||||
(f) => `
|
||||
<div class="rd-card" style="padding:1.5rem;">
|
||||
<div style="font-size:1.75rem;margin-bottom:0.75rem;">${f.icon}</div>
|
||||
<h3 style="font-size:1rem;font-weight:600;color:#e2e8f0;margin:0 0 0.5rem;">${f.title}</h3>
|
||||
<p style="font-size:0.875rem;color:#94a3b8;margin:0;line-height:1.5;">${f.desc}</p>
|
||||
</div>`,
|
||||
).join("")}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ── Ecosystem Integrations ── -->
|
||||
<section class="rd-section">
|
||||
<h2 style="text-align:center;font-size:1.25rem;font-weight:700;color:#f1f5f9;margin:0 0 1.5rem;">
|
||||
r* Ecosystem Integrations
|
||||
</h2>
|
||||
<div class="rd-grid rd-grid--3">
|
||||
${INTEGRATIONS.map(
|
||||
(i) => `
|
||||
<div class="rd-card" style="padding:1.25rem;">
|
||||
<div style="font-size:1.5rem;margin-bottom:0.5rem;">${i.icon}</div>
|
||||
<h3 style="font-size:0.875rem;font-weight:600;color:#e2e8f0;margin:0 0 0.375rem;">${i.name}</h3>
|
||||
<p style="font-size:0.8rem;color:#94a3b8;margin:0;line-height:1.4;">${i.desc}</p>
|
||||
</div>`,
|
||||
).join("")}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ── CTA ── -->
|
||||
<section class="rd-section rd-section--narrow">
|
||||
<div class="rd-cta">
|
||||
<h2>Coordinate in Time & Space</h2>
|
||||
<p>
|
||||
rCal layers temporal zoom, spatial context, and lunar cycles into a single calendar.
|
||||
Plan events that respect natural rhythms and local conditions.
|
||||
</p>
|
||||
<a href="/create-space" style="background:linear-gradient(135deg,#6366f1,#a78bfa);box-shadow:0 8px 24px rgba(99,102,241,0.25);">
|
||||
Create Your Space
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>`;
|
||||
}
|
||||
|
|
@ -9,12 +9,11 @@ import { Hono } from "hono";
|
|||
import { readFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
import { sql } from "../../shared/db/pool";
|
||||
import { renderShell, renderDemoShell } from "../../server/shell";
|
||||
import { renderShell } from "../../server/shell";
|
||||
import { getModuleInfoList } from "../../shared/module";
|
||||
import type { RSpaceModule } from "../../shared/module";
|
||||
import { verifyEncryptIDToken, extractToken } from "@encryptid/sdk/server";
|
||||
import { renderLanding } from "./landing";
|
||||
import { renderDemo } from "./demo";
|
||||
|
||||
const routes = new Hono();
|
||||
|
||||
|
|
@ -377,18 +376,6 @@ routes.get("/api/context/:tool", async (c) => {
|
|||
// ── Page route ──
|
||||
routes.get("/", (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
if (space === "demo") {
|
||||
return c.html(renderDemoShell({
|
||||
title: "rCal Demo — rSpace",
|
||||
moduleId: "rcal",
|
||||
spaceSlug: space,
|
||||
modules: getModuleInfoList(),
|
||||
theme: "dark",
|
||||
body: renderDemo(),
|
||||
scripts: `<script type="module" src="/modules/rcal/folk-calendar-view.js?v=3"></script>`,
|
||||
styles: `<link rel="stylesheet" href="/modules/rcal/cal.css?v=2">`,
|
||||
}));
|
||||
}
|
||||
return c.html(renderShell({
|
||||
title: `${space} — Calendar | rSpace`,
|
||||
moduleId: "rcal",
|
||||
|
|
@ -409,7 +396,6 @@ export const calModule: RSpaceModule = {
|
|||
routes,
|
||||
standaloneDomain: "rcal.online",
|
||||
landingPage: renderLanding,
|
||||
demoPage: renderDemo,
|
||||
feeds: [
|
||||
{
|
||||
id: "events",
|
||||
|
|
|
|||
|
|
@ -1,116 +0,0 @@
|
|||
/**
|
||||
* rCart demo page — server-rendered HTML body.
|
||||
*
|
||||
* Embeds the full <folk-cart-shop space="demo"> component for
|
||||
* real interactivity (catalog browsing, order tracking, filtering)
|
||||
* plus showcase sections explaining the rCart vision.
|
||||
*/
|
||||
|
||||
const FEATURES = [
|
||||
{
|
||||
icon: "\u{1F30D}",
|
||||
title: "Cosmolocal Fulfillment",
|
||||
desc: "Orders are matched to the nearest capable print shop or makerspace. Design global, manufacture local.",
|
||||
},
|
||||
{
|
||||
icon: "\u{1F6D2}",
|
||||
title: "Group Shopping",
|
||||
desc: "Communities pool resources and split costs. Transparent funding progress for every item in the cart.",
|
||||
},
|
||||
{
|
||||
icon: "\u{1F4B0}",
|
||||
title: "Revenue Splits",
|
||||
desc: "Every order automatically splits revenue between provider, creator, and community fund via rFunds flows.",
|
||||
},
|
||||
{
|
||||
icon: "\u{1F4E6}",
|
||||
title: "Order Tracking",
|
||||
desc: "Follow orders from pending through production to delivery. Real-time status updates across the community.",
|
||||
},
|
||||
];
|
||||
|
||||
const INTEGRATIONS = [
|
||||
{ icon: "\u{1F30A}", name: "rFunds", desc: "Revenue from orders flows through TBFF budget funnels with enoughness thresholds." },
|
||||
{ icon: "\u{1F3A8}", name: "rDesign", desc: "Design artifacts become print-ready catalog entries with one click." },
|
||||
{ icon: "\u{1F465}", name: "rNetwork", desc: "Provider registry matches orders to the closest maker in your network." },
|
||||
{ icon: "\u{1F5FA}", name: "rMaps", desc: "See provider locations and delivery zones on the map." },
|
||||
{ icon: "\u{1F4DD}", name: "rNotes", desc: "Link product specs, sizing guides, and design notes to catalog items." },
|
||||
{ icon: "\u2615\uFE0F", name: "rSpace", desc: "Each space has its own shop. Nest catalogs across spaces for cross-community commerce." },
|
||||
];
|
||||
|
||||
export function renderDemo(): string {
|
||||
return `
|
||||
<div class="rd-root" style="--rd-accent-from:#10b981; --rd-accent-to:#2dd4bf;">
|
||||
|
||||
<!-- Hero -->
|
||||
<section class="rd-hero">
|
||||
<div style="display:inline-block;padding:0.375rem 1rem;background:rgba(16,185,129,0.1);border:1px solid rgba(16,185,129,0.2);border-radius:9999px;font-size:0.875rem;color:#6ee7b7;font-weight:500;margin-bottom:1.5rem;">
|
||||
Cosmolocal Print-on-Demand
|
||||
</div>
|
||||
<h1>rCart Demo</h1>
|
||||
<p class="rd-subtitle">Group shopping with cosmolocal fulfillment, revenue splits, and transparent funding</p>
|
||||
<div class="rd-meta">
|
||||
<span>\u{1F6D2} Catalog & Orders</span>
|
||||
<span style="color:#475569">|</span>
|
||||
<span>\u{1F30D} Local Fulfillment</span>
|
||||
<span style="color:#475569">|</span>
|
||||
<span>\u{1F4B0} Revenue Splits</span>
|
||||
<span style="color:#475569">|</span>
|
||||
<span>\u{1F4E6} Order Tracking</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Interactive Shop -->
|
||||
<section class="rd-section rd-section--narrow">
|
||||
<div class="rd-card" style="padding:0;overflow:hidden;">
|
||||
<folk-cart-shop space="demo"></folk-cart-shop>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Core Concepts -->
|
||||
<section class="rd-section">
|
||||
<div class="rd-grid rd-grid--2">
|
||||
${FEATURES.map(
|
||||
(f) => `
|
||||
<div class="rd-card" style="padding:1.5rem;">
|
||||
<div style="font-size:1.75rem;margin-bottom:0.75rem;">${f.icon}</div>
|
||||
<h3 style="font-size:1rem;font-weight:600;color:#e2e8f0;margin:0 0 0.5rem;">${f.title}</h3>
|
||||
<p style="font-size:0.875rem;color:#94a3b8;margin:0;line-height:1.5;">${f.desc}</p>
|
||||
</div>`,
|
||||
).join("")}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Ecosystem Integrations -->
|
||||
<section class="rd-section">
|
||||
<h2 style="text-align:center;font-size:1.25rem;font-weight:700;color:#f1f5f9;margin:0 0 1.5rem;">
|
||||
r* Ecosystem Integrations
|
||||
</h2>
|
||||
<div class="rd-grid rd-grid--3">
|
||||
${INTEGRATIONS.map(
|
||||
(i) => `
|
||||
<div class="rd-card" style="padding:1.25rem;">
|
||||
<div style="font-size:1.5rem;margin-bottom:0.5rem;">${i.icon}</div>
|
||||
<h3 style="font-size:0.875rem;font-weight:600;color:#e2e8f0;margin:0 0 0.375rem;">${i.name}</h3>
|
||||
<p style="font-size:0.8rem;color:#94a3b8;margin:0;line-height:1.4;">${i.desc}</p>
|
||||
</div>`,
|
||||
).join("")}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CTA -->
|
||||
<section class="rd-section rd-section--narrow">
|
||||
<div class="rd-cta">
|
||||
<h2>Ready to shop together?</h2>
|
||||
<p>
|
||||
rCart gives your community a shared catalog with cosmolocal fulfillment,
|
||||
transparent funding, and automatic revenue splits.
|
||||
</p>
|
||||
<a href="/create-space" style="background:linear-gradient(135deg,#10b981,#059669);box-shadow:0 8px 24px rgba(16,185,129,0.25);">
|
||||
Create Your First Cart
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>`;
|
||||
}
|
||||
|
|
@ -10,13 +10,12 @@ import { Hono } from "hono";
|
|||
import { readFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
import { sql } from "../../shared/db/pool";
|
||||
import { renderShell, renderDemoShell } from "../../server/shell";
|
||||
import { renderShell } from "../../server/shell";
|
||||
import { getModuleInfoList } from "../../shared/module";
|
||||
import { depositOrderRevenue } from "./flow";
|
||||
import type { RSpaceModule } from "../../shared/module";
|
||||
import { verifyEncryptIDToken, extractToken } from "@encryptid/sdk/server";
|
||||
import { renderLanding } from "./landing";
|
||||
import { renderDemo } from "./demo";
|
||||
|
||||
const routes = new Hono();
|
||||
|
||||
|
|
@ -443,20 +442,8 @@ routes.post("/api/fulfill/resolve", async (c) => {
|
|||
// ── Page route: shop ──
|
||||
routes.get("/", (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
if (space === "demo") {
|
||||
return c.html(renderDemoShell({
|
||||
title: "rCart Demo — rSpace",
|
||||
moduleId: "rcart",
|
||||
spaceSlug: space,
|
||||
modules: getModuleInfoList(),
|
||||
theme: "dark",
|
||||
body: renderDemo(),
|
||||
scripts: `<script type="module" src="/modules/rcart/folk-cart-shop.js"></script>`,
|
||||
styles: `<link rel="stylesheet" href="/modules/rcart/cart.css">`,
|
||||
}));
|
||||
}
|
||||
return c.html(renderShell({
|
||||
title: `Shop | rSpace`,
|
||||
title: `${space} — Shop | rSpace`,
|
||||
moduleId: "rcart",
|
||||
spaceSlug: space,
|
||||
modules: getModuleInfoList(),
|
||||
|
|
@ -475,7 +462,6 @@ export const cartModule: RSpaceModule = {
|
|||
routes,
|
||||
standaloneDomain: "rcart.online",
|
||||
landingPage: renderLanding,
|
||||
demoPage: renderDemo,
|
||||
feeds: [
|
||||
{
|
||||
id: "orders",
|
||||
|
|
|
|||
|
|
@ -1,116 +0,0 @@
|
|||
/**
|
||||
* rFunds demo page — server-rendered HTML body.
|
||||
*
|
||||
* Embeds the full <folk-funds-app space="demo" mode="demo"> component
|
||||
* for real interactivity (flow listing, river visualization, TBFF diagrams)
|
||||
* plus showcase sections explaining the rFunds vision.
|
||||
*/
|
||||
|
||||
const FEATURES = [
|
||||
{
|
||||
icon: "\u{1F30A}",
|
||||
title: "River Visualization",
|
||||
desc: "Watch resources flow through animated Sankey rivers. Sources feed into funnels, funnels feed outcomes, and surplus overflows to where it's needed most.",
|
||||
},
|
||||
{
|
||||
icon: "\u{1F4CA}",
|
||||
title: "TBFF Flows",
|
||||
desc: "Threshold-Based Funding Flows distribute resources based on enoughness. When a funnel is sufficient, surplus flows to the next highest-need area.",
|
||||
},
|
||||
{
|
||||
icon: "\u{1F4B8}",
|
||||
title: "Treasury Management",
|
||||
desc: "Track deposits, withdrawals, and allocations across all community funnels. Transparent financial governance in real time.",
|
||||
},
|
||||
{
|
||||
icon: "\u2696\uFE0F",
|
||||
title: "Enoughness Layer",
|
||||
desc: "Each funnel has a sufficiency threshold. Golden glow indicates a funded funnel. Resources keep flowing until everyone has enough.",
|
||||
},
|
||||
];
|
||||
|
||||
const INTEGRATIONS = [
|
||||
{ icon: "\u{1F6D2}", name: "rCart", desc: "Order revenue flows through TBFF funnels with automatic creator/provider/community splits." },
|
||||
{ icon: "\u{1F5F3}", name: "rVote", desc: "Governance votes determine funding priorities and threshold adjustments." },
|
||||
{ icon: "\u{2708}\uFE0F", name: "rTrips", desc: "Group expenses feed into shared budget flows with per-person tracking." },
|
||||
{ icon: "\u{1F4DD}", name: "rNotes", desc: "Attach budget rationales, meeting minutes, and audit logs to flows." },
|
||||
{ icon: "\u{1F4C5}", name: "rCal", desc: "Budget reviews, treasury snapshots, and governance votes on the calendar timeline." },
|
||||
{ icon: "\u2615\uFE0F", name: "rSpace", desc: "Each space has its own treasury. Nest flows across spaces for multi-community coordination." },
|
||||
];
|
||||
|
||||
export function renderDemo(): string {
|
||||
return `
|
||||
<div class="rd-root" style="--rd-accent-from:#f59e0b; --rd-accent-to:#10b981;">
|
||||
|
||||
<!-- Hero -->
|
||||
<section class="rd-hero">
|
||||
<div style="display:inline-block;padding:0.375rem 1rem;background:rgba(245,158,11,0.1);border:1px solid rgba(245,158,11,0.2);border-radius:9999px;font-size:0.875rem;color:#fcd34d;font-weight:500;margin-bottom:1.5rem;">
|
||||
Threshold-Based Funding Flows
|
||||
</div>
|
||||
<h1>rFunds Demo</h1>
|
||||
<p class="rd-subtitle">Budget flows, river visualization, and treasury management with enoughness thresholds</p>
|
||||
<div class="rd-meta">
|
||||
<span>\u{1F30A} River Visualization</span>
|
||||
<span style="color:#475569">|</span>
|
||||
<span>\u{1F4CA} TBFF Flows</span>
|
||||
<span style="color:#475569">|</span>
|
||||
<span>\u{1F4B8} Treasury</span>
|
||||
<span style="color:#475569">|</span>
|
||||
<span>\u2696\uFE0F Enoughness</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Interactive Funds App -->
|
||||
<section class="rd-section rd-section--narrow">
|
||||
<div class="rd-card" style="padding:0;overflow:hidden;">
|
||||
<folk-funds-app space="demo" mode="demo"></folk-funds-app>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Core Concepts -->
|
||||
<section class="rd-section">
|
||||
<div class="rd-grid rd-grid--2">
|
||||
${FEATURES.map(
|
||||
(f) => `
|
||||
<div class="rd-card" style="padding:1.5rem;">
|
||||
<div style="font-size:1.75rem;margin-bottom:0.75rem;">${f.icon}</div>
|
||||
<h3 style="font-size:1rem;font-weight:600;color:#e2e8f0;margin:0 0 0.5rem;">${f.title}</h3>
|
||||
<p style="font-size:0.875rem;color:#94a3b8;margin:0;line-height:1.5;">${f.desc}</p>
|
||||
</div>`,
|
||||
).join("")}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Ecosystem Integrations -->
|
||||
<section class="rd-section">
|
||||
<h2 style="text-align:center;font-size:1.25rem;font-weight:700;color:#f1f5f9;margin:0 0 1.5rem;">
|
||||
r* Ecosystem Integrations
|
||||
</h2>
|
||||
<div class="rd-grid rd-grid--3">
|
||||
${INTEGRATIONS.map(
|
||||
(i) => `
|
||||
<div class="rd-card" style="padding:1.25rem;">
|
||||
<div style="font-size:1.5rem;margin-bottom:0.5rem;">${i.icon}</div>
|
||||
<h3 style="font-size:0.875rem;font-weight:600;color:#e2e8f0;margin:0 0 0.375rem;">${i.name}</h3>
|
||||
<p style="font-size:0.8rem;color:#94a3b8;margin:0;line-height:1.4;">${i.desc}</p>
|
||||
</div>`,
|
||||
).join("")}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CTA -->
|
||||
<section class="rd-section rd-section--narrow">
|
||||
<div class="rd-cta">
|
||||
<h2>Design Your Funding Flows</h2>
|
||||
<p>
|
||||
rFunds lets your community design transparent budget flows with threshold-based
|
||||
mechanisms, enoughness scoring, and animated river visualizations.
|
||||
</p>
|
||||
<a href="/create-space" style="background:linear-gradient(135deg,#f59e0b,#10b981);box-shadow:0 8px 24px rgba(245,158,11,0.25);">
|
||||
Create Your Space
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>`;
|
||||
}
|
||||
|
|
@ -8,12 +8,11 @@ import { Hono } from "hono";
|
|||
import { readFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
import { sql } from "../../shared/db/pool";
|
||||
import { renderShell, renderDemoShell } from "../../server/shell";
|
||||
import { renderShell } from "../../server/shell";
|
||||
import type { RSpaceModule } from "../../shared/module";
|
||||
import { getModuleInfoList } from "../../shared/module";
|
||||
import { verifyEncryptIDToken, extractToken } from "@encryptid/sdk/server";
|
||||
import { renderLanding } from "./landing";
|
||||
import { renderDemo } from "./demo";
|
||||
|
||||
const FLOW_SERVICE_URL = process.env.FLOW_SERVICE_URL || "http://payment-flow:3010";
|
||||
|
||||
|
|
@ -198,20 +197,8 @@ const fundsStyles = `<link rel="stylesheet" href="/modules/rfunds/funds.css">`;
|
|||
// Landing page
|
||||
routes.get("/", (c) => {
|
||||
const spaceSlug = c.req.param("space") || "demo";
|
||||
if (spaceSlug === "demo") {
|
||||
return c.html(renderDemoShell({
|
||||
title: "rFunds Demo — rSpace",
|
||||
moduleId: "rfunds",
|
||||
spaceSlug,
|
||||
modules: getModuleInfoList(),
|
||||
theme: "dark",
|
||||
body: renderDemo(),
|
||||
scripts: `<script type="module" src="/modules/rfunds/folk-funds-app.js"></script>`,
|
||||
styles: `<link rel="stylesheet" href="/modules/rfunds/funds.css">`,
|
||||
}));
|
||||
}
|
||||
return c.html(renderShell({
|
||||
title: `rFunds — TBFF Flow Funding | rSpace`,
|
||||
title: `${spaceSlug} — Funds | rSpace`,
|
||||
moduleId: "rfunds",
|
||||
spaceSlug,
|
||||
modules: getModuleInfoList(),
|
||||
|
|
@ -260,7 +247,6 @@ export const fundsModule: RSpaceModule = {
|
|||
description: "Budget flows, river visualization, and treasury management",
|
||||
routes,
|
||||
landingPage: renderLanding,
|
||||
demoPage: renderDemo,
|
||||
standaloneDomain: "rfunds.online",
|
||||
feeds: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,116 +0,0 @@
|
|||
/**
|
||||
* rNotes demo page — server-rendered HTML body.
|
||||
*
|
||||
* Embeds the full <folk-notes-app space="demo"> component for
|
||||
* real interactivity (notebook browsing, note editing, search, tags)
|
||||
* plus showcase sections explaining the rNotes vision.
|
||||
*/
|
||||
|
||||
const FEATURES = [
|
||||
{
|
||||
icon: "\u{1F3A4}",
|
||||
title: "Live Transcription",
|
||||
desc: "Record and transcribe in real time. Stream audio via WebSocket or transcribe offline with Parakeet.js.",
|
||||
},
|
||||
{
|
||||
icon: "\u270F\uFE0F",
|
||||
title: "Rich Editing",
|
||||
desc: "Headings, lists, code blocks, highlights, images, and file attachments in every note.",
|
||||
},
|
||||
{
|
||||
icon: "\u{1F4D3}",
|
||||
title: "Notebooks",
|
||||
desc: "Organize notes into notebooks with sections. Nest as deep as you need for any project structure.",
|
||||
},
|
||||
{
|
||||
icon: "\u{1F3F7}\uFE0F",
|
||||
title: "Flexible Tags",
|
||||
desc: "Cross-cutting tags let you find notes across all notebooks instantly. Filter and search by any combination.",
|
||||
},
|
||||
];
|
||||
|
||||
const INTEGRATIONS = [
|
||||
{ icon: "\u{1F4C5}", name: "rCal", desc: "Link notes to calendar events. Meeting agendas, daily journals, and retrospective logs." },
|
||||
{ icon: "\u{1F5FA}", name: "rMaps", desc: "Pin location-aware notes to places on the map. Field notes, venue reviews, site reports." },
|
||||
{ icon: "\u{1F465}", name: "rNetwork", desc: "Collaborate on notes across your network with real-time Automerge sync." },
|
||||
{ icon: "\u{1F3AC}", name: "rTube", desc: "Attach meeting notes, transcripts, and timestamps to video recordings." },
|
||||
{ icon: "\u{1F5F3}", name: "rVote", desc: "Link governance proposals to supporting research notes and discussion threads." },
|
||||
{ icon: "\u2615\uFE0F", name: "rSpace", desc: "Pin any note to the collaborative canvas. Each space has its own knowledge base." },
|
||||
];
|
||||
|
||||
export function renderDemo(): string {
|
||||
return `
|
||||
<div class="rd-root" style="--rd-accent-from:#f59e0b; --rd-accent-to:#fb923c;">
|
||||
|
||||
<!-- Hero -->
|
||||
<section class="rd-hero">
|
||||
<div style="display:inline-block;padding:0.375rem 1rem;background:rgba(245,158,11,0.1);border:1px solid rgba(245,158,11,0.2);border-radius:9999px;font-size:0.875rem;color:#fbbf24;font-weight:500;margin-bottom:1.5rem;">
|
||||
Collaborative Knowledge Base
|
||||
</div>
|
||||
<h1>rNotes Demo</h1>
|
||||
<p class="rd-subtitle">Notebooks with rich-text notes, voice transcription, and real-time collaboration</p>
|
||||
<div class="rd-meta">
|
||||
<span>\u{1F3A4} Transcription</span>
|
||||
<span style="color:#475569">|</span>
|
||||
<span>\u270F\uFE0F Rich Editing</span>
|
||||
<span style="color:#475569">|</span>
|
||||
<span>\u{1F4D3} Notebooks</span>
|
||||
<span style="color:#475569">|</span>
|
||||
<span>\u{1F3F7}\uFE0F Tags & Search</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Interactive Notes App -->
|
||||
<section class="rd-section rd-section--narrow">
|
||||
<div class="rd-card" style="padding:0;overflow:hidden;">
|
||||
<folk-notes-app space="demo"></folk-notes-app>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Core Concepts -->
|
||||
<section class="rd-section">
|
||||
<div class="rd-grid rd-grid--2">
|
||||
${FEATURES.map(
|
||||
(f) => `
|
||||
<div class="rd-card" style="padding:1.5rem;">
|
||||
<div style="font-size:1.75rem;margin-bottom:0.75rem;">${f.icon}</div>
|
||||
<h3 style="font-size:1rem;font-weight:600;color:#e2e8f0;margin:0 0 0.5rem;">${f.title}</h3>
|
||||
<p style="font-size:0.875rem;color:#94a3b8;margin:0;line-height:1.5;">${f.desc}</p>
|
||||
</div>`,
|
||||
).join("")}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Ecosystem Integrations -->
|
||||
<section class="rd-section">
|
||||
<h2 style="text-align:center;font-size:1.25rem;font-weight:700;color:#f1f5f9;margin:0 0 1.5rem;">
|
||||
r* Ecosystem Integrations
|
||||
</h2>
|
||||
<div class="rd-grid rd-grid--3">
|
||||
${INTEGRATIONS.map(
|
||||
(i) => `
|
||||
<div class="rd-card" style="padding:1.25rem;">
|
||||
<div style="font-size:1.5rem;margin-bottom:0.5rem;">${i.icon}</div>
|
||||
<h3 style="font-size:0.875rem;font-weight:600;color:#e2e8f0;margin:0 0 0.375rem;">${i.name}</h3>
|
||||
<p style="font-size:0.8rem;color:#94a3b8;margin:0;line-height:1.4;">${i.desc}</p>
|
||||
</div>`,
|
||||
).join("")}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CTA -->
|
||||
<section class="rd-section rd-section--narrow">
|
||||
<div class="rd-cta">
|
||||
<h2>Ready to capture everything?</h2>
|
||||
<p>
|
||||
rNotes gives your team a shared knowledge base with rich editing, flexible organization,
|
||||
and deep integration with the r* ecosystem — all on a collaborative canvas.
|
||||
</p>
|
||||
<a href="/create-space" style="background:linear-gradient(135deg,#f59e0b,#f97316);box-shadow:0 8px 24px rgba(245,158,11,0.25);">
|
||||
Start Taking Notes
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>`;
|
||||
}
|
||||
|
|
@ -9,12 +9,11 @@ import { Hono } from "hono";
|
|||
import { readFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
import { sql } from "../../shared/db/pool";
|
||||
import { renderShell, renderDemoShell } from "../../server/shell";
|
||||
import { renderShell } from "../../server/shell";
|
||||
import { getModuleInfoList } from "../../shared/module";
|
||||
import type { RSpaceModule } from "../../shared/module";
|
||||
import { verifyEncryptIDToken, extractToken } from "@encryptid/sdk/server";
|
||||
import { renderLanding } from "./landing";
|
||||
import { renderDemo } from "./demo";
|
||||
|
||||
const routes = new Hono();
|
||||
|
||||
|
|
@ -363,18 +362,6 @@ routes.delete("/api/notes/:id", async (c) => {
|
|||
// ── Page route ──
|
||||
routes.get("/", (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
if (space === "demo") {
|
||||
return c.html(renderDemoShell({
|
||||
title: "rNotes Demo — rSpace",
|
||||
moduleId: "rnotes",
|
||||
spaceSlug: space,
|
||||
modules: getModuleInfoList(),
|
||||
theme: "dark",
|
||||
body: renderDemo(),
|
||||
scripts: `<script type="module" src="/modules/rnotes/folk-notes-app.js"></script>`,
|
||||
styles: `<link rel="stylesheet" href="/modules/rnotes/notes.css">`,
|
||||
}));
|
||||
}
|
||||
return c.html(renderShell({
|
||||
title: `${space} — Notes | rSpace`,
|
||||
moduleId: "rnotes",
|
||||
|
|
@ -394,7 +381,6 @@ export const notesModule: RSpaceModule = {
|
|||
description: "Notebooks with rich-text notes, voice transcription, and collaboration",
|
||||
routes,
|
||||
landingPage: renderLanding,
|
||||
demoPage: renderDemo,
|
||||
standaloneDomain: "rnotes.online",
|
||||
feeds: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,116 +0,0 @@
|
|||
/**
|
||||
* rTrips demo page — server-rendered HTML body.
|
||||
*
|
||||
* Embeds the full <folk-trips-planner space="demo"> component for
|
||||
* real interactivity (trip list, destinations, itinerary, bookings,
|
||||
* expenses, packing lists) plus showcase sections explaining the rTrips vision.
|
||||
*/
|
||||
|
||||
const FEATURES = [
|
||||
{
|
||||
icon: "\u{1F5FA}",
|
||||
title: "Destinations",
|
||||
desc: "Pin destinations on the map with arrival/departure dates, country info, and notes. Reorder your route with drag and drop.",
|
||||
},
|
||||
{
|
||||
icon: "\u{1F4C5}",
|
||||
title: "Itinerary",
|
||||
desc: "Plan day-by-day activities grouped by date. Categories include hiking, dining, sightseeing, transit, and more.",
|
||||
},
|
||||
{
|
||||
icon: "\u{1F4B0}",
|
||||
title: "Expense Splitting",
|
||||
desc: "Track group expenses with automatic per-person splits. See who paid what and who owes whom.",
|
||||
},
|
||||
{
|
||||
icon: "\u{1F392}",
|
||||
title: "Packing Lists",
|
||||
desc: "Collaborative packing checklists organized by category. Check items off as you pack — synced in real time.",
|
||||
},
|
||||
];
|
||||
|
||||
const INTEGRATIONS = [
|
||||
{ icon: "\u{1F5FA}", name: "rMaps", desc: "Destinations and routes appear on the interactive map with pins and driving directions." },
|
||||
{ icon: "\u{1F4C5}", name: "rCal", desc: "Trip dates, activities, and bookings sync to the community calendar." },
|
||||
{ icon: "\u{1F30A}", name: "rFunds", desc: "Group expenses feed into shared budget flows with threshold-based splits." },
|
||||
{ icon: "\u{1F5F3}", name: "rVote", desc: "Vote on daily activities, restaurants, and route decisions as a group." },
|
||||
{ icon: "\u{1F4DD}", name: "rNotes", desc: "Attach travel journals, packing tips, and logistics notes to the trip." },
|
||||
{ icon: "\u2615\uFE0F", name: "rSpace", desc: "Each trip lives on its own canvas with maps, notes, polls, and expenses connected." },
|
||||
];
|
||||
|
||||
export function renderDemo(): string {
|
||||
return `
|
||||
<div class="rd-root" style="--rd-accent-from:#14b8a6; --rd-accent-to:#06b6d4;">
|
||||
|
||||
<!-- Hero -->
|
||||
<section class="rd-hero">
|
||||
<div style="display:inline-block;padding:0.375rem 1rem;background:rgba(20,184,166,0.1);border:1px solid rgba(20,184,166,0.2);border-radius:9999px;font-size:0.875rem;color:#5eead4;font-weight:500;margin-bottom:1.5rem;">
|
||||
Collaborative Trip Planner
|
||||
</div>
|
||||
<h1>rTrips Demo</h1>
|
||||
<p class="rd-subtitle">Plan trips together with destinations, itinerary, bookings, expenses, and packing lists</p>
|
||||
<div class="rd-meta">
|
||||
<span>\u{1F5FA} Destinations</span>
|
||||
<span style="color:#475569">|</span>
|
||||
<span>\u{1F4C5} Itinerary</span>
|
||||
<span style="color:#475569">|</span>
|
||||
<span>\u{1F4B0} Expenses</span>
|
||||
<span style="color:#475569">|</span>
|
||||
<span>\u{1F392} Packing</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Interactive Trips Planner -->
|
||||
<section class="rd-section rd-section--narrow">
|
||||
<div class="rd-card" style="padding:0;overflow:hidden;">
|
||||
<folk-trips-planner space="demo"></folk-trips-planner>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Core Concepts -->
|
||||
<section class="rd-section">
|
||||
<div class="rd-grid rd-grid--2">
|
||||
${FEATURES.map(
|
||||
(f) => `
|
||||
<div class="rd-card" style="padding:1.5rem;">
|
||||
<div style="font-size:1.75rem;margin-bottom:0.75rem;">${f.icon}</div>
|
||||
<h3 style="font-size:1rem;font-weight:600;color:#e2e8f0;margin:0 0 0.5rem;">${f.title}</h3>
|
||||
<p style="font-size:0.875rem;color:#94a3b8;margin:0;line-height:1.5;">${f.desc}</p>
|
||||
</div>`,
|
||||
).join("")}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Ecosystem Integrations -->
|
||||
<section class="rd-section">
|
||||
<h2 style="text-align:center;font-size:1.25rem;font-weight:700;color:#f1f5f9;margin:0 0 1.5rem;">
|
||||
r* Ecosystem Integrations
|
||||
</h2>
|
||||
<div class="rd-grid rd-grid--3">
|
||||
${INTEGRATIONS.map(
|
||||
(i) => `
|
||||
<div class="rd-card" style="padding:1.25rem;">
|
||||
<div style="font-size:1.5rem;margin-bottom:0.5rem;">${i.icon}</div>
|
||||
<h3 style="font-size:0.875rem;font-weight:600;color:#e2e8f0;margin:0 0 0.375rem;">${i.name}</h3>
|
||||
<p style="font-size:0.8rem;color:#94a3b8;margin:0;line-height:1.4;">${i.desc}</p>
|
||||
</div>`,
|
||||
).join("")}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CTA -->
|
||||
<section class="rd-section rd-section--narrow">
|
||||
<div class="rd-cta">
|
||||
<h2>Plan Your Next Adventure</h2>
|
||||
<p>
|
||||
rTrips gives your group everything you need — routes, schedules, polls,
|
||||
shared expenses, and packing lists — all connected in one trip canvas.
|
||||
</p>
|
||||
<a href="/create-space" style="background:linear-gradient(135deg,#14b8a6,#06b6d4);box-shadow:0 8px 24px rgba(20,184,166,0.25);">
|
||||
Start Planning
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>`;
|
||||
}
|
||||
|
|
@ -9,12 +9,11 @@ import { Hono } from "hono";
|
|||
import { readFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
import { sql } from "../../shared/db/pool";
|
||||
import { renderShell, renderDemoShell } from "../../server/shell";
|
||||
import { renderShell } from "../../server/shell";
|
||||
import { getModuleInfoList } from "../../shared/module";
|
||||
import type { RSpaceModule } from "../../shared/module";
|
||||
import { verifyEncryptIDToken, extractToken } from "@encryptid/sdk/server";
|
||||
import { renderLanding } from "./landing";
|
||||
import { renderDemo } from "./demo";
|
||||
|
||||
const OSRM_URL = process.env.OSRM_URL || "http://osrm-backend:5000";
|
||||
|
||||
|
|
@ -255,18 +254,6 @@ routes.get("/routes", (c) => {
|
|||
// ── Page route ──
|
||||
routes.get("/", (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
if (space === "demo") {
|
||||
return c.html(renderDemoShell({
|
||||
title: "rTrips Demo — rSpace",
|
||||
moduleId: "rtrips",
|
||||
spaceSlug: space,
|
||||
modules: getModuleInfoList(),
|
||||
theme: "dark",
|
||||
body: renderDemo(),
|
||||
scripts: `<script type="module" src="/modules/rtrips/folk-trips-planner.js"></script>`,
|
||||
styles: `<link rel="stylesheet" href="/modules/rtrips/trips.css">`,
|
||||
}));
|
||||
}
|
||||
return c.html(renderShell({
|
||||
title: `${space} — Trips | rSpace`,
|
||||
moduleId: "rtrips",
|
||||
|
|
@ -286,7 +273,6 @@ export const tripsModule: RSpaceModule = {
|
|||
description: "Collaborative trip planner with itinerary, bookings, and expense splitting",
|
||||
routes,
|
||||
landingPage: renderLanding,
|
||||
demoPage: renderDemo,
|
||||
standaloneDomain: "rtrips.online",
|
||||
feeds: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,116 +0,0 @@
|
|||
/**
|
||||
* rTube demo page — server-rendered HTML body.
|
||||
*
|
||||
* Embeds the full <folk-video-player space="demo"> component for
|
||||
* real interactivity (video library, search, playback, live streaming)
|
||||
* plus showcase sections explaining the rTube vision.
|
||||
*/
|
||||
|
||||
const FEATURES = [
|
||||
{
|
||||
icon: "\u{1F3AC}",
|
||||
title: "Video Library",
|
||||
desc: "Browse, search, and play videos from your community's R2-backed storage. Supports MP4, WebM, MOV, and more.",
|
||||
},
|
||||
{
|
||||
icon: "\u{1F4E1}",
|
||||
title: "Live Streaming",
|
||||
desc: "Broadcast live via RTMP from OBS Studio or any streaming software. Viewers watch in real-time with HLS playback.",
|
||||
},
|
||||
{
|
||||
icon: "\u{1F4E4}",
|
||||
title: "Easy Uploads",
|
||||
desc: "Authenticated members upload videos directly. Files stream to Cloudflare R2 with automatic format detection.",
|
||||
},
|
||||
{
|
||||
icon: "\u{1F517}",
|
||||
title: "Direct Links",
|
||||
desc: "Copy shareable links to any video. HTTP range requests enable efficient streaming and seeking.",
|
||||
},
|
||||
];
|
||||
|
||||
const INTEGRATIONS = [
|
||||
{ icon: "\u{1F4DD}", name: "rNotes", desc: "Attach meeting notes, transcripts, and timestamps to video recordings." },
|
||||
{ icon: "\u{1F4C5}", name: "rCal", desc: "Scheduled recordings and live streams appear on the community calendar." },
|
||||
{ icon: "\u{1F465}", name: "rNetwork", desc: "Share videos across your network. Collaborative viewing and commenting." },
|
||||
{ icon: "\u{1F4DA}", name: "rBooks", desc: "Embed video content in publications and educational materials." },
|
||||
{ icon: "\u{1F5FA}", name: "rMaps", desc: "Geotagged videos appear on the map at their recording location." },
|
||||
{ icon: "\u2615\uFE0F", name: "rSpace", desc: "Each space has its own video library. Pin videos to the collaborative canvas." },
|
||||
];
|
||||
|
||||
export function renderDemo(): string {
|
||||
return `
|
||||
<div class="rd-root" style="--rd-accent-from:#ef4444; --rd-accent-to:#ec4899;">
|
||||
|
||||
<!-- Hero -->
|
||||
<section class="rd-hero">
|
||||
<div style="display:inline-block;padding:0.375rem 1rem;background:rgba(239,68,68,0.1);border:1px solid rgba(239,68,68,0.2);border-radius:9999px;font-size:0.875rem;color:#fca5a5;font-weight:500;margin-bottom:1.5rem;">
|
||||
Community Video Hosting
|
||||
</div>
|
||||
<h1>rTube Demo</h1>
|
||||
<p class="rd-subtitle">Video library, live streaming, and uploads powered by Cloudflare R2</p>
|
||||
<div class="rd-meta">
|
||||
<span>\u{1F3AC} Video Library</span>
|
||||
<span style="color:#475569">|</span>
|
||||
<span>\u{1F4E1} Live Streaming</span>
|
||||
<span style="color:#475569">|</span>
|
||||
<span>\u{1F4E4} Uploads</span>
|
||||
<span style="color:#475569">|</span>
|
||||
<span>\u{1F517} Direct Links</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Interactive Video Player -->
|
||||
<section class="rd-section rd-section--narrow">
|
||||
<div class="rd-card" style="padding:0;overflow:hidden;">
|
||||
<folk-video-player space="demo"></folk-video-player>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Core Concepts -->
|
||||
<section class="rd-section">
|
||||
<div class="rd-grid rd-grid--2">
|
||||
${FEATURES.map(
|
||||
(f) => `
|
||||
<div class="rd-card" style="padding:1.5rem;">
|
||||
<div style="font-size:1.75rem;margin-bottom:0.75rem;">${f.icon}</div>
|
||||
<h3 style="font-size:1rem;font-weight:600;color:#e2e8f0;margin:0 0 0.5rem;">${f.title}</h3>
|
||||
<p style="font-size:0.875rem;color:#94a3b8;margin:0;line-height:1.5;">${f.desc}</p>
|
||||
</div>`,
|
||||
).join("")}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Ecosystem Integrations -->
|
||||
<section class="rd-section">
|
||||
<h2 style="text-align:center;font-size:1.25rem;font-weight:700;color:#f1f5f9;margin:0 0 1.5rem;">
|
||||
r* Ecosystem Integrations
|
||||
</h2>
|
||||
<div class="rd-grid rd-grid--3">
|
||||
${INTEGRATIONS.map(
|
||||
(i) => `
|
||||
<div class="rd-card" style="padding:1.25rem;">
|
||||
<div style="font-size:1.5rem;margin-bottom:0.5rem;">${i.icon}</div>
|
||||
<h3 style="font-size:0.875rem;font-weight:600;color:#e2e8f0;margin:0 0 0.375rem;">${i.name}</h3>
|
||||
<p style="font-size:0.8rem;color:#94a3b8;margin:0;line-height:1.4;">${i.desc}</p>
|
||||
</div>`,
|
||||
).join("")}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CTA -->
|
||||
<section class="rd-section rd-section--narrow">
|
||||
<div class="rd-cta">
|
||||
<h2>Host Your Video Library</h2>
|
||||
<p>
|
||||
rTube gives your community private video hosting with streaming,
|
||||
uploads, and live broadcasting — all powered by Cloudflare R2.
|
||||
</p>
|
||||
<a href="/create-space" style="background:linear-gradient(135deg,#ef4444,#ec4899);box-shadow:0 8px 24px rgba(239,68,68,0.25);">
|
||||
Create Your Space
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>`;
|
||||
}
|
||||
|
|
@ -6,12 +6,11 @@
|
|||
*/
|
||||
|
||||
import { Hono } from "hono";
|
||||
import { renderShell, renderDemoShell } from "../../server/shell";
|
||||
import { renderShell } from "../../server/shell";
|
||||
import { getModuleInfoList } from "../../shared/module";
|
||||
import type { RSpaceModule } from "../../shared/module";
|
||||
import { verifyEncryptIDToken, extractToken } from "@encryptid/sdk/server";
|
||||
import { renderLanding } from "./landing";
|
||||
import { renderDemo } from "./demo";
|
||||
import { S3Client, ListObjectsV2Command, GetObjectCommand, HeadObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3";
|
||||
|
||||
const routes = new Hono();
|
||||
|
|
@ -193,18 +192,6 @@ routes.get("/api/health", (c) => c.json({ ok: true }));
|
|||
// ── Page route ──
|
||||
routes.get("/", (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
if (space === "demo") {
|
||||
return c.html(renderDemoShell({
|
||||
title: "rTube Demo — rSpace",
|
||||
moduleId: "rtube",
|
||||
spaceSlug: space,
|
||||
modules: getModuleInfoList(),
|
||||
theme: "dark",
|
||||
body: renderDemo(),
|
||||
scripts: `<script type="module" src="/modules/rtube/folk-video-player.js"></script>`,
|
||||
styles: `<link rel="stylesheet" href="/modules/rtube/tube.css">`,
|
||||
}));
|
||||
}
|
||||
return c.html(renderShell({
|
||||
title: `${space} — Tube | rSpace`,
|
||||
moduleId: "rtube",
|
||||
|
|
@ -224,7 +211,6 @@ export const tubeModule: RSpaceModule = {
|
|||
description: "Community video hosting & live streaming",
|
||||
routes,
|
||||
landingPage: renderLanding,
|
||||
demoPage: renderDemo,
|
||||
standaloneDomain: "rtube.online",
|
||||
feeds: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,116 +0,0 @@
|
|||
/**
|
||||
* rVote demo page — server-rendered HTML body.
|
||||
*
|
||||
* Embeds the full <folk-vote-dashboard space="demo"> component for
|
||||
* real interactivity (browse spaces, create proposals, cast conviction
|
||||
* votes, binary final votes) plus showcase sections explaining the rVote vision.
|
||||
*/
|
||||
|
||||
const FEATURES = [
|
||||
{
|
||||
icon: "\u{1F4CA}",
|
||||
title: "Conviction Voting",
|
||||
desc: "Stake credits on proposals you support. Weight costs credits quadratically (weight\u00B2), preventing plutocratic capture while rewarding conviction.",
|
||||
},
|
||||
{
|
||||
icon: "\u{1F3AF}",
|
||||
title: "Promotion Threshold",
|
||||
desc: "Proposals accumulate conviction score. When they hit the threshold, they auto-promote to a binary YES/NO/ABSTAIN final vote.",
|
||||
},
|
||||
{
|
||||
icon: "\u23F3",
|
||||
title: "Vote Decay",
|
||||
desc: "Conviction decays after 30 days. This ensures ongoing relevance — stale votes fade, keeping governance dynamic and current.",
|
||||
},
|
||||
{
|
||||
icon: "\u{1F3DB}\uFE0F",
|
||||
title: "Governance Spaces",
|
||||
desc: "Each community gets its own voting space with configurable thresholds, credit budgets, and voting periods.",
|
||||
},
|
||||
];
|
||||
|
||||
const INTEGRATIONS = [
|
||||
{ icon: "\u{1F30A}", name: "rFunds", desc: "Passed proposals trigger funding flows. Vote on budget allocations and threshold adjustments." },
|
||||
{ icon: "\u{1F4DD}", name: "rNotes", desc: "Link supporting research, discussion threads, and rationale documents to proposals." },
|
||||
{ icon: "\u{1F4C5}", name: "rCal", desc: "Voting deadlines, governance meetings, and proposal review periods on the calendar." },
|
||||
{ icon: "\u{1F465}", name: "rNetwork", desc: "Delegate voting power to trusted network members. Liquid democracy across communities." },
|
||||
{ icon: "\u{1F6D2}", name: "rCart", desc: "Vote on merchandise decisions, provider selection, and catalog curation." },
|
||||
{ icon: "\u2615\uFE0F", name: "rSpace", desc: "Each space has its own governance layer. Nest voting across spaces for multi-community decisions." },
|
||||
];
|
||||
|
||||
export function renderDemo(): string {
|
||||
return `
|
||||
<div class="rd-root" style="--rd-accent-from:#f97316; --rd-accent-to:#fb923c;">
|
||||
|
||||
<!-- Hero -->
|
||||
<section class="rd-hero">
|
||||
<div style="display:inline-block;padding:0.375rem 1rem;background:rgba(249,115,22,0.1);border:1px solid rgba(249,115,22,0.2);border-radius:9999px;font-size:0.875rem;color:#fdba74;font-weight:500;margin-bottom:1.5rem;">
|
||||
Conviction Voting Engine
|
||||
</div>
|
||||
<h1>rVote Demo</h1>
|
||||
<p class="rd-subtitle">Credit-weighted conviction voting for collaborative governance decisions</p>
|
||||
<div class="rd-meta">
|
||||
<span>\u{1F4CA} Conviction Voting</span>
|
||||
<span style="color:#475569">|</span>
|
||||
<span>\u{1F3AF} Thresholds</span>
|
||||
<span style="color:#475569">|</span>
|
||||
<span>\u23F3 Vote Decay</span>
|
||||
<span style="color:#475569">|</span>
|
||||
<span>\u{1F3DB}\uFE0F Governance</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Interactive Vote Dashboard -->
|
||||
<section class="rd-section rd-section--narrow">
|
||||
<div class="rd-card" style="padding:0;overflow:hidden;">
|
||||
<folk-vote-dashboard space="demo"></folk-vote-dashboard>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Core Concepts -->
|
||||
<section class="rd-section">
|
||||
<div class="rd-grid rd-grid--2">
|
||||
${FEATURES.map(
|
||||
(f) => `
|
||||
<div class="rd-card" style="padding:1.5rem;">
|
||||
<div style="font-size:1.75rem;margin-bottom:0.75rem;">${f.icon}</div>
|
||||
<h3 style="font-size:1rem;font-weight:600;color:#e2e8f0;margin:0 0 0.5rem;">${f.title}</h3>
|
||||
<p style="font-size:0.875rem;color:#94a3b8;margin:0;line-height:1.5;">${f.desc}</p>
|
||||
</div>`,
|
||||
).join("")}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Ecosystem Integrations -->
|
||||
<section class="rd-section">
|
||||
<h2 style="text-align:center;font-size:1.25rem;font-weight:700;color:#f1f5f9;margin:0 0 1.5rem;">
|
||||
r* Ecosystem Integrations
|
||||
</h2>
|
||||
<div class="rd-grid rd-grid--3">
|
||||
${INTEGRATIONS.map(
|
||||
(i) => `
|
||||
<div class="rd-card" style="padding:1.25rem;">
|
||||
<div style="font-size:1.5rem;margin-bottom:0.5rem;">${i.icon}</div>
|
||||
<h3 style="font-size:0.875rem;font-weight:600;color:#e2e8f0;margin:0 0 0.375rem;">${i.name}</h3>
|
||||
<p style="font-size:0.8rem;color:#94a3b8;margin:0;line-height:1.4;">${i.desc}</p>
|
||||
</div>`,
|
||||
).join("")}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CTA -->
|
||||
<section class="rd-section rd-section--narrow">
|
||||
<div class="rd-cta">
|
||||
<h2>Govern Together</h2>
|
||||
<p>
|
||||
rVote brings conviction-weighted governance to your community.
|
||||
Proposals rise through collective conviction, not majority rule.
|
||||
</p>
|
||||
<a href="/create-space" style="background:linear-gradient(135deg,#f97316,#fb923c);box-shadow:0 8px 24px rgba(249,115,22,0.25);">
|
||||
Create Your Space
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>`;
|
||||
}
|
||||
|
|
@ -9,9 +9,8 @@ import { Hono } from "hono";
|
|||
import { readFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
import { sql } from "../../shared/db/pool";
|
||||
import { renderShell, renderDemoShell } from "../../server/shell";
|
||||
import { renderShell } from "../../server/shell";
|
||||
import { getModuleInfoList } from "../../shared/module";
|
||||
import { renderDemo } from "./demo";
|
||||
import type { RSpaceModule } from "../../shared/module";
|
||||
import { verifyEncryptIDToken, extractToken } from "@encryptid/sdk/server";
|
||||
import { renderLanding } from "./landing";
|
||||
|
|
@ -329,18 +328,6 @@ routes.post("/api/proposals/:id/final-vote", async (c) => {
|
|||
// ── Page route ──
|
||||
routes.get("/", (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
if (space === "demo") {
|
||||
return c.html(renderDemoShell({
|
||||
title: "rVote Demo — rSpace",
|
||||
moduleId: "rvote",
|
||||
spaceSlug: space,
|
||||
modules: getModuleInfoList(),
|
||||
theme: "dark",
|
||||
body: renderDemo(),
|
||||
scripts: `<script type="module" src="/modules/rvote/folk-vote-dashboard.js"></script>`,
|
||||
styles: `<link rel="stylesheet" href="/modules/rvote/vote.css">`,
|
||||
}));
|
||||
}
|
||||
return c.html(renderShell({
|
||||
title: `${space} — Vote | rSpace`,
|
||||
moduleId: "rvote",
|
||||
|
|
@ -361,7 +348,6 @@ export const voteModule: RSpaceModule = {
|
|||
routes,
|
||||
standaloneDomain: "rvote.online",
|
||||
landingPage: renderLanding,
|
||||
demoPage: renderDemo,
|
||||
feeds: [
|
||||
{
|
||||
id: "proposals",
|
||||
|
|
|
|||
233
server/shell.ts
233
server/shell.ts
|
|
@ -645,18 +645,6 @@ const WELCOME_CSS = `
|
|||
`;
|
||||
|
||||
|
||||
/**
|
||||
* Render a demo page shell: standard shell + DEMO_PAGE_CSS + demo scripts.
|
||||
* Used by modules that have a demoPage() renderer.
|
||||
*/
|
||||
export function renderDemoShell(opts: ShellOptions & { demoScripts?: string }): string {
|
||||
return renderShell({
|
||||
...opts,
|
||||
styles: `${opts.styles || ""}\n<style>${DEMO_PAGE_CSS}</style>`,
|
||||
scripts: `${opts.scripts || ""}\n${opts.demoScripts || ""}`,
|
||||
});
|
||||
}
|
||||
|
||||
// ── Module landing page (bare-domain rspace.online/{moduleId}) ──
|
||||
|
||||
export interface ModuleLandingOptions {
|
||||
|
|
@ -982,227 +970,6 @@ export const RICH_LANDING_CSS = `
|
|||
|
||||
// ── Demo page CSS utilities (rd-* prefix, parallel to rl-* landing pages) ──
|
||||
|
||||
export const DEMO_PAGE_CSS = `
|
||||
/* ── Demo page base ── */
|
||||
.rd-root {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, #0f172a 0%, #1e293b 50%, #0f172a 100%);
|
||||
color: white; padding-bottom: 2rem;
|
||||
}
|
||||
.rd-hero {
|
||||
max-width: 48rem; margin: 0 auto; text-align: center;
|
||||
padding: 3rem 1.5rem 2rem;
|
||||
}
|
||||
.rd-hero h1 {
|
||||
font-size: 2.25rem; font-weight: 700; margin-bottom: 1rem;
|
||||
background: linear-gradient(135deg, var(--rd-accent-from, #14b8a6), var(--rd-accent-to, #22d3ee));
|
||||
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
@media (min-width: 640px) { .rd-hero h1 { font-size: 3rem; } }
|
||||
.rd-hero .rd-subtitle { font-size: 1.1rem; color: #cbd5e1; margin-bottom: 0.5rem; }
|
||||
.rd-hero .rd-meta { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 1rem; font-size: 0.875rem; color: #94a3b8; margin-bottom: 1.5rem; }
|
||||
.rd-hero .rd-meta span:not(:last-child)::after { content: ""; }
|
||||
|
||||
/* Avatars */
|
||||
.rd-avatars { display: flex; align-items: center; justify-content: center; gap: 0.5rem; }
|
||||
.rd-avatar {
|
||||
width: 2.5rem; height: 2.5rem; border-radius: 9999px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 0.875rem; font-weight: 700; color: white;
|
||||
box-shadow: 0 0 0 2px #1e293b;
|
||||
}
|
||||
.rd-avatars .rd-count { font-size: 0.875rem; color: #94a3b8; margin-left: 0.5rem; }
|
||||
|
||||
/* Cards */
|
||||
.rd-card {
|
||||
background: rgba(30,41,59,0.5); border-radius: 1rem;
|
||||
border: 1px solid rgba(51,65,85,0.5); overflow: hidden;
|
||||
}
|
||||
.rd-card-header {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
padding: 0.75rem 1.25rem; border-bottom: 1px solid rgba(51,65,85,0.5);
|
||||
}
|
||||
.rd-card-header .rd-card-title { display: flex; align-items: center; gap: 0.5rem; font-weight: 600; font-size: 0.875rem; }
|
||||
.rd-card-header .rd-card-title .rd-icon { font-size: 1.25rem; }
|
||||
.rd-card-header .rd-open-link {
|
||||
font-size: 0.75rem; padding: 0.375rem 0.75rem;
|
||||
background: rgba(51,65,85,0.6); border-radius: 0.5rem;
|
||||
color: #cbd5e1; text-decoration: none; transition: all 0.15s;
|
||||
}
|
||||
.rd-card-header .rd-open-link:hover { background: rgba(71,85,105,0.6); color: white; }
|
||||
.rd-card-body { padding: 1.25rem; }
|
||||
|
||||
/* Live badge */
|
||||
.rd-live {
|
||||
display: inline-flex; align-items: center; gap: 0.375rem;
|
||||
font-size: 0.75rem; color: #34d399;
|
||||
}
|
||||
.rd-live::before {
|
||||
content: ""; width: 0.375rem; height: 0.375rem; border-radius: 9999px;
|
||||
background: #34d399; animation: rd-pulse 2s ease-in-out infinite;
|
||||
}
|
||||
@keyframes rd-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } }
|
||||
|
||||
/* Status badge */
|
||||
.rd-status {
|
||||
display: inline-flex; align-items: center; gap: 0.375rem;
|
||||
font-size: 0.75rem; padding: 0.25rem 0.625rem; border-radius: 9999px;
|
||||
}
|
||||
.rd-status--connected { color: #34d399; background: rgba(52,211,153,0.1); border: 1px solid rgba(52,211,153,0.2); }
|
||||
.rd-status--disconnected { color: #f87171; background: rgba(248,113,113,0.1); border: 1px solid rgba(248,113,113,0.2); }
|
||||
.rd-status::before {
|
||||
content: ""; width: 0.5rem; height: 0.5rem; border-radius: 9999px;
|
||||
}
|
||||
.rd-status--connected::before { background: #34d399; }
|
||||
.rd-status--disconnected::before { background: #f87171; }
|
||||
|
||||
/* Progress bar */
|
||||
.rd-progress { height: 0.75rem; border-radius: 9999px; background: rgba(51,65,85,1); overflow: hidden; }
|
||||
.rd-progress--sm { height: 0.375rem; }
|
||||
.rd-progress--xs { height: 0.25rem; }
|
||||
.rd-progress__fill {
|
||||
height: 100%; border-radius: 9999px; transition: width 0.3s ease-out;
|
||||
background: linear-gradient(90deg, var(--rd-accent-from, #14b8a6), var(--rd-accent-to, #2dd4bf));
|
||||
}
|
||||
.rd-progress__fill--emerald { background: #10b981; }
|
||||
.rd-progress__fill--sky { background: #0ea5e9; }
|
||||
.rd-progress__fill--amber { background: #f59e0b; }
|
||||
.rd-progress__fill--rose { background: #f43f5e; }
|
||||
.rd-progress__fill--orange { background: linear-gradient(90deg, #fb923c, #f97316); }
|
||||
.rd-progress__fill--teal { background: linear-gradient(90deg, #14b8a6, #2dd4bf); }
|
||||
.rd-progress__fill--cyan { background: #06b6d4; }
|
||||
.rd-progress__fill--violet { background: #8b5cf6; }
|
||||
|
||||
/* Grid */
|
||||
.rd-grid { display: grid; gap: 1rem; }
|
||||
.rd-grid--2 { grid-template-columns: 1fr; }
|
||||
.rd-grid--3 { grid-template-columns: 1fr; }
|
||||
.rd-grid--4 { grid-template-columns: 1fr 1fr; }
|
||||
@media (min-width: 640px) {
|
||||
.rd-grid--2 { grid-template-columns: repeat(2, 1fr); }
|
||||
.rd-grid--3 { grid-template-columns: repeat(3, 1fr); }
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.rd-grid--3 { grid-template-columns: repeat(3, 1fr); }
|
||||
.rd-grid--4 { grid-template-columns: repeat(4, 1fr); }
|
||||
}
|
||||
|
||||
/* Section */
|
||||
.rd-section { max-width: 72rem; margin: 0 auto; padding: 0 1.5rem 1.5rem; }
|
||||
.rd-section--narrow { max-width: 64rem; }
|
||||
|
||||
/* Badge */
|
||||
.rd-badge {
|
||||
display: inline-block; font-size: 0.75rem; font-weight: 500;
|
||||
padding: 0.125rem 0.625rem; border-radius: 9999px;
|
||||
}
|
||||
.rd-badge--emerald { background: rgba(16,185,129,0.2); color: #6ee7b7; }
|
||||
.rd-badge--sky { background: rgba(14,165,233,0.2); color: #7dd3fc; }
|
||||
.rd-badge--amber { background: rgba(245,158,11,0.2); color: #fcd34d; }
|
||||
.rd-badge--rose { background: rgba(244,63,94,0.2); color: #fda4af; }
|
||||
.rd-badge--orange { background: rgba(249,115,22,0.2); color: #fdba74; }
|
||||
.rd-badge--teal { background: rgba(20,184,166,0.2); color: #5eead4; }
|
||||
.rd-badge--slate { background: rgba(100,116,139,0.2); color: #94a3b8; }
|
||||
|
||||
/* Stat box */
|
||||
.rd-stat { background: rgba(51,65,85,0.3); border-radius: 0.75rem; padding: 1rem; text-align: center; }
|
||||
.rd-stat__value { font-size: 1.5rem; font-weight: 700; color: white; }
|
||||
.rd-stat__label { font-size: 0.75rem; color: #94a3b8; margin-top: 0.25rem; }
|
||||
.rd-stat__sub { font-size: 0.75rem; color: #64748b; margin-top: 0.125rem; }
|
||||
|
||||
/* Button */
|
||||
.rd-btn {
|
||||
display: inline-flex; align-items: center; gap: 0.5rem;
|
||||
padding: 0.5rem 1rem; border-radius: 0.5rem; font-size: 0.875rem;
|
||||
font-weight: 500; cursor: pointer; border: none; transition: all 0.15s;
|
||||
}
|
||||
.rd-btn--ghost { background: rgba(51,65,85,0.6); color: #cbd5e1; }
|
||||
.rd-btn--ghost:hover { background: rgba(71,85,105,0.6); color: white; }
|
||||
.rd-btn--ghost:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
.rd-btn--primary { color: white; }
|
||||
|
||||
/* Divider row */
|
||||
.rd-row {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
padding: 0.75rem 1.25rem; border-top: 1px solid rgba(51,65,85,0.5);
|
||||
}
|
||||
|
||||
/* Item row (expense/cart list item) */
|
||||
.rd-item {
|
||||
display: flex; align-items: center; gap: 1rem;
|
||||
padding: 0.75rem 1.25rem; transition: background 0.1s;
|
||||
}
|
||||
.rd-item:hover { background: rgba(51,65,85,0.2); }
|
||||
.rd-item + .rd-item { border-top: 1px solid rgba(51,65,85,0.3); }
|
||||
|
||||
/* Checkbox */
|
||||
.rd-checkbox {
|
||||
width: 1.25rem; height: 1.25rem; border-radius: 0.25rem; flex-shrink: 0;
|
||||
border: 2px solid #475569; display: flex; align-items: center; justify-content: center;
|
||||
cursor: pointer; transition: all 0.15s;
|
||||
}
|
||||
.rd-checkbox--checked { background: var(--rd-accent-from, #14b8a6); border-color: var(--rd-accent-from, #14b8a6); }
|
||||
.rd-checkbox svg { width: 0.75rem; height: 0.75rem; color: white; }
|
||||
.rd-checkbox:hover { border-color: #64748b; }
|
||||
|
||||
/* CTA section */
|
||||
.rd-cta {
|
||||
background: rgba(30,41,59,0.5); border-radius: 1rem;
|
||||
border: 1px solid rgba(51,65,85,0.5); padding: 2.5rem;
|
||||
text-align: center; margin-top: 1rem;
|
||||
}
|
||||
.rd-cta h2 { font-size: 1.875rem; font-weight: 700; margin-bottom: 0.75rem; }
|
||||
.rd-cta p { color: #94a3b8; max-width: 32rem; margin: 0 auto 1.5rem; font-size: 0.875rem; }
|
||||
.rd-cta a {
|
||||
display: inline-block; padding: 0.875rem 2rem; border-radius: 0.75rem;
|
||||
font-size: 1.1rem; font-weight: 500; color: white; text-decoration: none;
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
.rd-cta a:hover { transform: translateY(-2px); }
|
||||
|
||||
/* Text helpers */
|
||||
.rd-text-muted { color: #94a3b8; }
|
||||
.rd-text-dim { color: #64748b; }
|
||||
.rd-text-sm { font-size: 0.875rem; }
|
||||
.rd-text-xs { font-size: 0.75rem; }
|
||||
.rd-text-center { text-align: center; }
|
||||
.rd-font-bold { font-weight: 700; }
|
||||
.rd-font-semibold { font-weight: 600; }
|
||||
.rd-font-medium { font-weight: 500; }
|
||||
.rd-truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.rd-line-through { text-decoration: line-through; }
|
||||
.rd-hidden { display: none !important; }
|
||||
|
||||
/* Color helpers */
|
||||
.rd-emerald { color: #34d399; }
|
||||
.rd-rose { color: #fb7185; }
|
||||
.rd-amber { color: #fbbf24; }
|
||||
.rd-cyan { color: #22d3ee; }
|
||||
.rd-teal { color: #2dd4bf; }
|
||||
.rd-orange { color: #fb923c; }
|
||||
.rd-sky { color: #38bdf8; }
|
||||
.rd-violet { color: #a78bfa; }
|
||||
|
||||
/* BG helpers */
|
||||
.rd-bg-emerald { background: #10b981; }
|
||||
.rd-bg-cyan { background: #06b6d4; }
|
||||
.rd-bg-violet { background: #8b5cf6; }
|
||||
.rd-bg-amber { background: #f59e0b; }
|
||||
.rd-bg-rose { background: #f43f5e; }
|
||||
.rd-bg-teal { background: #14b8a6; }
|
||||
.rd-bg-sky { background: #0ea5e9; }
|
||||
.rd-bg-orange { background: #f97316; }
|
||||
.rd-bg-slate { background: #64748b; }
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 640px) {
|
||||
.rd-hero { padding: 2rem 1rem 1.5rem; }
|
||||
.rd-hero h1 { font-size: 2rem; }
|
||||
.rd-section { padding: 0 1rem 1rem; }
|
||||
}
|
||||
`;
|
||||
|
||||
export function escapeHtml(s: string): string {
|
||||
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -153,7 +153,7 @@ export class RStackAppSwitcher extends HTMLElement {
|
|||
<a class="rstack-header" href="https://rstack.online" target="_blank" rel="noopener">
|
||||
<span class="rstack-badge">r*</span>
|
||||
<div class="rstack-info">
|
||||
<span class="rstack-title">rSpace</span>
|
||||
<span class="rstack-title">rStack</span>
|
||||
<span class="rstack-subtitle">Self-hosted community app suite</span>
|
||||
</div>
|
||||
</a>
|
||||
|
|
|
|||
|
|
@ -54,8 +54,6 @@ export interface RSpaceModule {
|
|||
url: string;
|
||||
name: string;
|
||||
};
|
||||
/** Optional: render rich demo page body HTML (served when space === "demo") */
|
||||
demoPage?: () => string;
|
||||
}
|
||||
|
||||
/** Registry of all loaded modules */
|
||||
|
|
|
|||
Loading…
Reference in New Issue