Merge branch 'dev'
This commit is contained in:
commit
18e19ddac8
|
|
@ -0,0 +1,118 @@
|
|||
/**
|
||||
* rBooks — rich landing page body.
|
||||
* Returned by landingPage() in the module export;
|
||||
* the shell wraps it with header, CSS, and analytics.
|
||||
*/
|
||||
export function renderLanding(): string {
|
||||
const demo = "https://demo.rspace.online/rbooks";
|
||||
|
||||
return `
|
||||
<!-- Hero -->
|
||||
<section class="rl-hero">
|
||||
<span class="rl-tagline">rBooks</span>
|
||||
<h1 class="rl-heading">Your Community's Library</h1>
|
||||
<p class="rl-subtext">
|
||||
Upload, share, and read PDFs together. A beautiful flipbook reader,
|
||||
searchable catalog, and community contributions — all self-hosted.
|
||||
</p>
|
||||
<div class="rl-cta-row">
|
||||
<a href="${demo}" class="rl-cta-primary" id="ml-primary">Start Your Library</a>
|
||||
<a href="/create-space" class="rl-cta-secondary">Create a Space</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- How It Works -->
|
||||
<section class="rl-section">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center">How It Works</h2>
|
||||
<div class="rl-grid-3">
|
||||
<div class="rl-step">
|
||||
<div class="rl-step__num">1</div>
|
||||
<h3>Upload PDFs</h3>
|
||||
<p>Drag-and-drop any PDF. Add title, author, tags, and a license — it is processed instantly.</p>
|
||||
</div>
|
||||
<div class="rl-step">
|
||||
<div class="rl-step__num">2</div>
|
||||
<h3>Browse the Library</h3>
|
||||
<p>Search by title, author, or tag. Featured books rise to the top.</p>
|
||||
</div>
|
||||
<div class="rl-step">
|
||||
<div class="rl-step__num">3</div>
|
||||
<h3>Read with Flipbook</h3>
|
||||
<p>Open any book in the interactive flipbook reader. Turn pages, zoom in, and bookmark your place.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Features -->
|
||||
<section class="rl-section rl-section--alt">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center">Features</h2>
|
||||
<div class="rl-grid-4">
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">📖</div>
|
||||
<h3>Flipbook Reader</h3>
|
||||
<p>Realistic page-turn animations, zoom, and full-screen mode for a natural reading experience.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">⚙</div>
|
||||
<h3>PDF Processing</h3>
|
||||
<p>Automatic page counting, metadata extraction, and optimized delivery to the reader.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">📚</div>
|
||||
<h3>Space Libraries</h3>
|
||||
<p>Each community space gets its own curated collection with search and tagging.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">👥</div>
|
||||
<h3>Community Contributions</h3>
|
||||
<p>Members upload and share. View counts and download stats track engagement.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Ecosystem Integration -->
|
||||
<section class="rl-section">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center">Works With the Ecosystem</h2>
|
||||
<p class="rl-subtext" style="text-align:center">
|
||||
rBooks connects to other rSpace modules to extend your library.
|
||||
</p>
|
||||
<div class="rl-grid-2">
|
||||
<div class="rl-integration">
|
||||
<div class="rl-icon-box">📜</div>
|
||||
<div>
|
||||
<h3><a href="/rpubs" style="color:#14b8a6;text-decoration:none">rPubs</a></h3>
|
||||
<p>Write and publish your own books with the collaborative editor, then list them in rBooks.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rl-integration">
|
||||
<div class="rl-icon-box">🛒</div>
|
||||
<div>
|
||||
<h3><a href="/rcart" style="color:#14b8a6;text-decoration:none">rCart</a></h3>
|
||||
<p>Sell premium PDFs through your community storefront with integrated checkout.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CTA -->
|
||||
<section class="rl-section rl-section--alt">
|
||||
<div class="rl-container" style="text-align:center">
|
||||
<h2 class="rl-heading">Ready to Build Your Library?</h2>
|
||||
<p class="rl-subtext">
|
||||
Upload your first PDF and let your community start reading.
|
||||
</p>
|
||||
<div class="rl-cta-row">
|
||||
<a href="${demo}" class="rl-cta-primary">Start Your Library</a>
|
||||
<a href="/create-space" class="rl-cta-secondary">Create a Space</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="rl-back"><a href="/">← Back to rSpace</a></div>`;
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ import { sql } from "../../shared/db/pool";
|
|||
import { renderShell } from "../../server/shell";
|
||||
import { getModuleInfoList } from "../../shared/module";
|
||||
import type { RSpaceModule } from "../../shared/module";
|
||||
import { renderLanding } from "./landing";
|
||||
import {
|
||||
verifyEncryptIDToken,
|
||||
extractToken,
|
||||
|
|
@ -301,6 +302,7 @@ export const booksModule: RSpaceModule = {
|
|||
description: "Community PDF library with flipbook reader",
|
||||
routes,
|
||||
standaloneDomain: "rbooks.online",
|
||||
landingPage: renderLanding,
|
||||
feeds: [
|
||||
{
|
||||
id: "reading-list",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,266 @@
|
|||
/**
|
||||
* rCal landing page — relational calendar.
|
||||
*/
|
||||
export function renderLanding(): string {
|
||||
return `
|
||||
<!-- Hero -->
|
||||
<div class="rl-hero">
|
||||
<span class="rl-tagline">rCal</span>
|
||||
<h1 class="rl-heading">Time is shared.<br>Your calendar should be too.</h1>
|
||||
<p class="rl-subtext">
|
||||
A spatiotemporal calendar that couples where and when, supports natural cycles,
|
||||
and zooms from 30-second moments to geological epochs.
|
||||
</p>
|
||||
<div class="rl-cta-row">
|
||||
<a href="https://demo.rspace.online/rcal" class="rl-cta-primary" id="ml-primary">Try the Demo</a>
|
||||
<a href="/create-space" class="rl-cta-secondary">Create a Space</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Principles -->
|
||||
<section class="rl-section">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center">Design principles</h2>
|
||||
<div class="rl-grid-4" style="margin-top:2rem">
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box"><span style="font-size:1.25rem">🤝</span></div>
|
||||
<h3>Shared by Default</h3>
|
||||
<p>Calendars belong to spaces, not individuals. Everyone sees the same schedule.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box"><span style="font-size:1.25rem">🗺</span></div>
|
||||
<h3>Spatiotemporal</h3>
|
||||
<p>Events have places, not just times. Where and when are coupled at every zoom level.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box"><span style="font-size:1.25rem">🌙</span></div>
|
||||
<h3>Natural Cycles</h3>
|
||||
<p>Lunar phases, solstices, and seasonal rhythms overlaid on the Gregorian grid.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box"><span style="font-size:1.25rem">🔭</span></div>
|
||||
<h3>Multi-Scale Zoom</h3>
|
||||
<p>Ten zoom levels from 30-second moments to cosmic time. One unified timeline.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Why rCal -->
|
||||
<section class="rl-section rl-section--alt">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center">Why rCal</h2>
|
||||
<div class="rl-grid-3" style="margin-top:2rem">
|
||||
<div class="rl-card">
|
||||
<h3>Where + When</h3>
|
||||
<p>Every event carries location context. Zoom into a city and see only what happens there.</p>
|
||||
</div>
|
||||
<div class="rl-card">
|
||||
<h3>Coupled Zoom</h3>
|
||||
<p>Temporal and spatial zoom are linked. Zoom out in time and the map zooms out too.</p>
|
||||
</div>
|
||||
<div class="rl-card">
|
||||
<h3>Multi-Source Sync</h3>
|
||||
<p>Pull events from ICS feeds, CalDAV servers, and other r* modules into one view.</p>
|
||||
</div>
|
||||
<div class="rl-card">
|
||||
<h3>Lunar Overlay</h3>
|
||||
<p>Moon phases computed and displayed alongside events. Plan by natural cycles.</p>
|
||||
</div>
|
||||
<div class="rl-card">
|
||||
<h3>r* Ecosystem Embeds</h3>
|
||||
<p>rVote deadlines, rWork sprints, rFunds milestones — all surface as calendar events.</p>
|
||||
</div>
|
||||
<div class="rl-card">
|
||||
<h3>Self-Hosted</h3>
|
||||
<p>Your data lives on your infrastructure. No vendor lock-in, no surveillance calendars.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Temporal Zoom -->
|
||||
<section class="rl-section">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center">Temporal zoom</h2>
|
||||
<p class="rl-subtext" style="text-align:center">Ten levels of time — from the blink of an eye to deep time.</p>
|
||||
<div class="rl-card" style="max-width:700px;margin:2rem auto 0">
|
||||
<div class="rl-zoom-bar">
|
||||
<div class="rl-zoom-bar__row">
|
||||
<span class="rl-zoom-bar__label">0</span>
|
||||
<div class="rl-zoom-bar__bar" style="width:10%;background:rgba(99,102,241,0.3)">
|
||||
<span class="rl-zoom-bar__name">Moment</span>
|
||||
</div>
|
||||
<span class="rl-zoom-bar__span">30 s</span>
|
||||
</div>
|
||||
<div class="rl-zoom-bar__row">
|
||||
<span class="rl-zoom-bar__label">1</span>
|
||||
<div class="rl-zoom-bar__bar" style="width:15%;background:rgba(99,102,241,0.28)">
|
||||
<span class="rl-zoom-bar__name">Minute</span>
|
||||
</div>
|
||||
<span class="rl-zoom-bar__span">1–10 min</span>
|
||||
</div>
|
||||
<div class="rl-zoom-bar__row">
|
||||
<span class="rl-zoom-bar__label">2</span>
|
||||
<div class="rl-zoom-bar__bar" style="width:22%;background:rgba(99,102,241,0.25)">
|
||||
<span class="rl-zoom-bar__name">Hour</span>
|
||||
</div>
|
||||
<span class="rl-zoom-bar__span">1–6 hrs</span>
|
||||
</div>
|
||||
<div class="rl-zoom-bar__row">
|
||||
<span class="rl-zoom-bar__label">3</span>
|
||||
<div class="rl-zoom-bar__bar" style="width:30%;background:rgba(99,102,241,0.22)">
|
||||
<span class="rl-zoom-bar__name">Day</span>
|
||||
</div>
|
||||
<span class="rl-zoom-bar__span">24 hrs</span>
|
||||
</div>
|
||||
<div class="rl-zoom-bar__row">
|
||||
<span class="rl-zoom-bar__label">4</span>
|
||||
<div class="rl-zoom-bar__bar" style="width:40%;background:rgba(99,102,241,0.20)">
|
||||
<span class="rl-zoom-bar__name">Week</span>
|
||||
</div>
|
||||
<span class="rl-zoom-bar__span">7 days</span>
|
||||
</div>
|
||||
<div class="rl-zoom-bar__row">
|
||||
<span class="rl-zoom-bar__label">5</span>
|
||||
<div class="rl-zoom-bar__bar" style="width:50%;background:rgba(99,102,241,0.18)">
|
||||
<span class="rl-zoom-bar__name">Lunar</span>
|
||||
</div>
|
||||
<span class="rl-zoom-bar__span">29.5 days</span>
|
||||
</div>
|
||||
<div class="rl-zoom-bar__row">
|
||||
<span class="rl-zoom-bar__label">6</span>
|
||||
<div class="rl-zoom-bar__bar" style="width:60%;background:rgba(99,102,241,0.16)">
|
||||
<span class="rl-zoom-bar__name">Season</span>
|
||||
</div>
|
||||
<span class="rl-zoom-bar__span">~90 days</span>
|
||||
</div>
|
||||
<div class="rl-zoom-bar__row">
|
||||
<span class="rl-zoom-bar__label">7</span>
|
||||
<div class="rl-zoom-bar__bar" style="width:70%;background:rgba(99,102,241,0.14)">
|
||||
<span class="rl-zoom-bar__name">Year</span>
|
||||
</div>
|
||||
<span class="rl-zoom-bar__span">365 days</span>
|
||||
</div>
|
||||
<div class="rl-zoom-bar__row">
|
||||
<span class="rl-zoom-bar__label">8</span>
|
||||
<div class="rl-zoom-bar__bar" style="width:85%;background:rgba(99,102,241,0.12)">
|
||||
<span class="rl-zoom-bar__name">Epoch</span>
|
||||
</div>
|
||||
<span class="rl-zoom-bar__span">decades–centuries</span>
|
||||
</div>
|
||||
<div class="rl-zoom-bar__row">
|
||||
<span class="rl-zoom-bar__label">9</span>
|
||||
<div class="rl-zoom-bar__bar" style="width:100%;background:rgba(99,102,241,0.10)">
|
||||
<span class="rl-zoom-bar__name">Cosmic</span>
|
||||
</div>
|
||||
<span class="rl-zoom-bar__span">geological</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Calendar Views -->
|
||||
<section class="rl-section rl-section--alt">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center">Four views</h2>
|
||||
<div class="rl-grid-4" style="margin-top:2rem">
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box"><span style="font-size:1.25rem">📅</span></div>
|
||||
<h3>Temporal</h3>
|
||||
<p>Classic calendar grid — day, week, month. The view you know.</p>
|
||||
<p style="font-size:0.7rem;color:#64748b;margin-top:0.5rem"><kbd style="background:rgba(255,255,255,0.1);padding:0.1rem 0.3rem;border-radius:3px;font-size:0.65rem">T</kbd> to switch</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box"><span style="font-size:1.25rem">🗺</span></div>
|
||||
<h3>Spatial</h3>
|
||||
<p>Map view with events pinned to locations. Zoom couples time and space.</p>
|
||||
<p style="font-size:0.7rem;color:#64748b;margin-top:0.5rem"><kbd style="background:rgba(255,255,255,0.1);padding:0.1rem 0.3rem;border-radius:3px;font-size:0.65rem">S</kbd> to switch</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box"><span style="font-size:1.25rem">🌙</span></div>
|
||||
<h3>Lunar</h3>
|
||||
<p>Moon-phase overlay with illumination percentages. Plan with natural rhythms.</p>
|
||||
<p style="font-size:0.7rem;color:#64748b;margin-top:0.5rem"><kbd style="background:rgba(255,255,255,0.1);padding:0.1rem 0.3rem;border-radius:3px;font-size:0.65rem">L</kbd> to switch</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box"><span style="font-size:1.25rem">🧩</span></div>
|
||||
<h3>Context</h3>
|
||||
<p>Events grouped by r* source module. See your rWork sprints next to rVote deadlines.</p>
|
||||
<p style="font-size:0.7rem;color:#64748b;margin-top:0.5rem"><kbd style="background:rgba(255,255,255,0.1);padding:0.1rem 0.3rem;border-radius:3px;font-size:0.65rem">C</kbd> to switch</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Ecosystem Integration -->
|
||||
<section class="rl-section">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center">Ecosystem integration</h2>
|
||||
<p class="rl-subtext" style="text-align:center">rCal pulls events from across the r* ecosystem automatically.</p>
|
||||
<div class="rl-grid-3" style="margin-top:2rem">
|
||||
<div class="rl-integration">
|
||||
<div class="rl-icon-box" style="flex-shrink:0"><span style="font-size:1.25rem">✈️</span></div>
|
||||
<div>
|
||||
<h3><a href="/rtrips" style="color:#e2e8f0;text-decoration:none">rTrips</a></h3>
|
||||
<p>Travel itineraries appear as multi-day calendar events with location pins.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rl-integration">
|
||||
<div class="rl-icon-box" style="flex-shrink:0"><span style="font-size:1.25rem">🗺</span></div>
|
||||
<div>
|
||||
<h3><a href="/rmaps" style="color:#e2e8f0;text-decoration:none">rMaps</a></h3>
|
||||
<p>Location-tagged events render on the spatial view map layer.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rl-integration">
|
||||
<div class="rl-icon-box" style="flex-shrink:0"><span style="font-size:1.25rem">👥</span></div>
|
||||
<div>
|
||||
<h3><a href="/rnetwork" style="color:#e2e8f0;text-decoration:none">rNetwork</a></h3>
|
||||
<p>Meeting events auto-link to participant profiles and relationship context.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rl-integration">
|
||||
<div class="rl-icon-box" style="flex-shrink:0"><span style="font-size:1.25rem">🛒</span></div>
|
||||
<div>
|
||||
<h3><a href="/rcart" style="color:#e2e8f0;text-decoration:none">rCart</a></h3>
|
||||
<p>Group-buy deadlines and fulfillment ETAs show up as calendar milestones.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rl-integration">
|
||||
<div class="rl-icon-box" style="flex-shrink:0"><span style="font-size:1.25rem">📝</span></div>
|
||||
<div>
|
||||
<h3><a href="/rnotes" style="color:#e2e8f0;text-decoration:none">rNotes</a></h3>
|
||||
<p>Meeting notes link back to the calendar event that spawned them.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rl-integration">
|
||||
<div class="rl-icon-box" style="flex-shrink:0"><span style="font-size:1.25rem">🌐</span></div>
|
||||
<div>
|
||||
<h3><a href="/" style="color:#e2e8f0;text-decoration:none">rSpace</a></h3>
|
||||
<p>Space-level milestones and deadlines aggregate across all modules into one timeline.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CTA -->
|
||||
<section class="rl-section rl-section--alt">
|
||||
<div class="rl-container" style="text-align:center">
|
||||
<h2 class="rl-heading">See time differently</h2>
|
||||
<p class="rl-subtext">
|
||||
A calendar that understands place, cycles, and community. Try the demo or create your own space.
|
||||
</p>
|
||||
<div class="rl-cta-row">
|
||||
<a href="https://demo.rspace.online/rcal" class="rl-cta-primary" id="ml-primary">Open rCal</a>
|
||||
<a href="/create-space" class="rl-cta-secondary">Create a Space</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="rl-back">
|
||||
<a href="/">← Back to rSpace</a>
|
||||
</div>`;
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ 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";
|
||||
|
||||
const routes = new Hono();
|
||||
|
||||
|
|
@ -394,6 +395,7 @@ export const calModule: RSpaceModule = {
|
|||
description: "Temporal coordination calendar with lunar, solar, and seasonal systems",
|
||||
routes,
|
||||
standaloneDomain: "rcal.online",
|
||||
landingPage: renderLanding,
|
||||
feeds: [
|
||||
{
|
||||
id: "events",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,127 @@
|
|||
/**
|
||||
* rCart landing page — group shopping, together.
|
||||
*/
|
||||
export function renderLanding(): string {
|
||||
return `
|
||||
<!-- Hero -->
|
||||
<div class="rl-hero">
|
||||
<span class="rl-tagline">rCart</span>
|
||||
<h1 class="rl-heading">Group Shopping, Together</h1>
|
||||
<p class="rl-subtext">
|
||||
A shared shopping cart for communities. Pool orders, unlock bulk pricing,
|
||||
and fulfill locally through the cosmolocal provider network.
|
||||
</p>
|
||||
<div class="rl-cta-row">
|
||||
<a href="https://demo.rspace.online/rcart" class="rl-cta-primary" id="ml-primary">Browse the Shop</a>
|
||||
<a href="/rpubs" class="rl-cta-secondary">Create with rPubs</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- How it works -->
|
||||
<section class="rl-section">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center">How it works</h2>
|
||||
<div class="rl-grid-3" style="margin-top:2rem">
|
||||
<div class="rl-step">
|
||||
<span class="rl-step__num">1</span>
|
||||
<h3>Create a Space</h3>
|
||||
<p>Your space is your shared shopping context. Members see the same catalog and cart.</p>
|
||||
</div>
|
||||
<div class="rl-step">
|
||||
<span class="rl-step__num">2</span>
|
||||
<h3>Add Products</h3>
|
||||
<p>List print-ready artifacts from rPubs, or browse what others have published.</p>
|
||||
</div>
|
||||
<div class="rl-step">
|
||||
<span class="rl-step__num">3</span>
|
||||
<h3>Pay Together</h3>
|
||||
<p>Pool orders to hit bulk pricing tiers. Pay with crypto or card. Revenue splits automatically.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Features -->
|
||||
<section class="rl-section rl-section--alt">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center">Features</h2>
|
||||
<div class="rl-grid-4" style="margin-top:2rem">
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="9" cy="21" r="1"/><circle cx="20" cy="21" r="1"/><path d="M1 1h4l2.68 13.39a2 2 0 002 1.61h9.72a2 2 0 002-1.61L23 6H6"/></svg>
|
||||
</div>
|
||||
<h3>Universal Cart</h3>
|
||||
<p>One cart shared across your space. Members add items, pool orders, and hit bulk thresholds together.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="5" width="20" height="14" rx="2"/><line x1="2" y1="10" x2="22" y2="10"/></svg>
|
||||
</div>
|
||||
<h3>Crypto + Card</h3>
|
||||
<p>Pay with credit card or cryptocurrency. x402 protocol support for instant crypto payments.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/><path d="M3.51 9a9 9 0 0114.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0020.49 15"/></svg>
|
||||
</div>
|
||||
<h3>Real-time Updates</h3>
|
||||
<p>Order status updates in real time — from placement through production to delivery.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
|
||||
</div>
|
||||
<h3>Passkey Login</h3>
|
||||
<p>Passwordless authentication with EncryptID passkeys. One tap to sign in across all r* apps.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Fulfillment -->
|
||||
<section class="rl-section">
|
||||
<div class="rl-container">
|
||||
<div class="rl-grid-2">
|
||||
<div>
|
||||
<h2 class="rl-heading">Cosmolocal fulfillment</h2>
|
||||
<p class="rl-subtext" style="margin-bottom:1.5rem">
|
||||
Every order is matched to the nearest capable provider. Design is global, manufacturing is local.
|
||||
</p>
|
||||
<ul class="rl-check-list">
|
||||
<li><strong>Provider matching</strong> — automatic routing by capability, location, and cost</li>
|
||||
<li><strong>Revenue splits</strong> — creator, community, and provider shares via rFunds</li>
|
||||
<li><strong>Order tracking</strong> — real-time status from accepted to delivered</li>
|
||||
<li><strong>Volume pricing</strong> — automatic tier detection from pooled orders</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center" style="display:flex;align-items:center;justify-content:center">
|
||||
<div>
|
||||
<div class="rl-icon-box" style="margin:0 auto 1rem">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="1" y="3" width="15" height="13"/><polygon points="16 8 20 8 23 11 23 16 16 16 16 8"/><circle cx="5.5" cy="18.5" r="2.5"/><circle cx="18.5" cy="18.5" r="2.5"/></svg>
|
||||
</div>
|
||||
<h3>Nearest Provider Wins</h3>
|
||||
<p>Less shipping, lower emissions,<br>faster delivery, local jobs.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CTA -->
|
||||
<section class="rl-section rl-section--alt">
|
||||
<div class="rl-container" style="text-align:center">
|
||||
<h2 class="rl-heading">Ready to shop together?</h2>
|
||||
<p class="rl-subtext">
|
||||
Browse the catalog, pool orders with your community, and support local providers.
|
||||
</p>
|
||||
<div class="rl-cta-row">
|
||||
<a href="https://demo.rspace.online/rcart" class="rl-cta-primary" id="ml-primary">Open rCart</a>
|
||||
<a href="/rpubs" class="rl-cta-secondary">Create with rPubs</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="rl-back">
|
||||
<a href="/">← Back to rSpace</a>
|
||||
</div>`;
|
||||
}
|
||||
|
|
@ -15,6 +15,7 @@ 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";
|
||||
|
||||
const routes = new Hono();
|
||||
|
||||
|
|
@ -460,6 +461,7 @@ export const cartModule: RSpaceModule = {
|
|||
description: "Cosmolocal print-on-demand shop",
|
||||
routes,
|
||||
standaloneDomain: "rcart.online",
|
||||
landingPage: renderLanding,
|
||||
feeds: [
|
||||
{
|
||||
id: "orders",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,120 @@
|
|||
/**
|
||||
* rChoices — rich landing page body.
|
||||
* Returned by landingPage() in the module export;
|
||||
* the shell wraps it with header, CSS, and analytics.
|
||||
*/
|
||||
export function renderLanding(): string {
|
||||
const demo = "https://demo.rspace.online/rchoices";
|
||||
|
||||
return `
|
||||
<!-- Hero -->
|
||||
<section class="rl-hero">
|
||||
<span class="rl-tagline">rChoices</span>
|
||||
<h1 class="rl-heading">Decide Together, Fairly</h1>
|
||||
<p class="rl-subtext">
|
||||
Quadratic voting, ranked choice, and multi-criteria scoring — all as
|
||||
interactive shapes on your canvas. Drop a choice, let members vote,
|
||||
watch results emerge in real time.
|
||||
</p>
|
||||
<div class="rl-cta-row">
|
||||
<a href="${demo}" class="rl-cta-primary" id="ml-primary">Make Better Decisions</a>
|
||||
<a href="/create-space" class="rl-cta-secondary">Create a Space</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- How It Works -->
|
||||
<section class="rl-section">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center">How It Works</h2>
|
||||
<div class="rl-grid-3">
|
||||
<div class="rl-step">
|
||||
<div class="rl-step__num">1</div>
|
||||
<h3>Create a Choice Shape</h3>
|
||||
<p>Pick a voting method, name it, and add options. The shape appears on the canvas.</p>
|
||||
</div>
|
||||
<div class="rl-step">
|
||||
<div class="rl-step__num">2</div>
|
||||
<h3>Members Vote</h3>
|
||||
<p>Space members interact with the shape to cast votes, rank preferences, or score criteria.</p>
|
||||
</div>
|
||||
<div class="rl-step">
|
||||
<div class="rl-step__num">3</div>
|
||||
<h3>Results Emerge</h3>
|
||||
<p>Live tallies update as votes arrive. View charts, rankings, and spider diagrams in real time.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Voting Methods -->
|
||||
<section class="rl-section rl-section--alt">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center">Voting Methods</h2>
|
||||
<p class="rl-subtext" style="text-align:center">
|
||||
Three powerful mechanisms, each designed for different decision contexts.
|
||||
</p>
|
||||
<div class="rl-grid-3">
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">⚖</div>
|
||||
<h3>Quadratic Voting</h3>
|
||||
<p>Express intensity of preference. The cost of additional votes on one option grows exponentially, balancing passion with fairness.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">📋</div>
|
||||
<h3>Ranked Choice</h3>
|
||||
<p>Order your preferences from first to last. Instant-runoff tabulation finds the option with the broadest support.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">🕸</div>
|
||||
<h3>Multi-Criteria Scoring</h3>
|
||||
<p>Score options across weighted attributes. Spider diagrams reveal trade-offs at a glance.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Features -->
|
||||
<section class="rl-section">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center">Features</h2>
|
||||
<div class="rl-grid-4">
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">🎨</div>
|
||||
<h3>Canvas Integration</h3>
|
||||
<p>Choice shapes live on the canvas alongside notes, images, and other shapes — full spatial context.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">⚡</div>
|
||||
<h3>Real-time Results</h3>
|
||||
<p>Tallies, rankings, and spider charts update live as members vote via Automerge CRDT sync.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">🔧</div>
|
||||
<h3>Configurable Parameters</h3>
|
||||
<p>Set vote budgets, deadlines, anonymity, and quorum thresholds per choice shape.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">☑</div>
|
||||
<h3>rVote Integration</h3>
|
||||
<p>Connect to <a href="/rvote" style="color:#14b8a6">rVote</a> for formal governance proposals backed by on-canvas choices.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CTA -->
|
||||
<section class="rl-section rl-section--alt">
|
||||
<div class="rl-container" style="text-align:center">
|
||||
<h2 class="rl-heading">Better Decisions Start Here</h2>
|
||||
<p class="rl-subtext">
|
||||
Drop a choice shape on your canvas and let your community weigh in.
|
||||
</p>
|
||||
<div class="rl-cta-row">
|
||||
<a href="${demo}" class="rl-cta-primary">Make Better Decisions</a>
|
||||
<a href="/create-space" class="rl-cta-secondary">Create a Space</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="rl-back"><a href="/">← Back to rSpace</a></div>`;
|
||||
}
|
||||
|
|
@ -11,6 +11,7 @@
|
|||
import { Hono } from "hono";
|
||||
import { renderShell } from "../../server/shell";
|
||||
import type { RSpaceModule } from "../../shared/module";
|
||||
import { renderLanding } from "./landing";
|
||||
import { getModuleInfoList } from "../../shared/module";
|
||||
import { getDocumentData } from "../../server/community-store";
|
||||
|
||||
|
|
@ -67,6 +68,7 @@ export const choicesModule: RSpaceModule = {
|
|||
description: "Polls, rankings, and multi-criteria scoring",
|
||||
routes,
|
||||
standaloneDomain: "rchoices.online",
|
||||
landingPage: renderLanding,
|
||||
feeds: [
|
||||
{
|
||||
id: "poll-results",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,120 @@
|
|||
/**
|
||||
* rData — rich landing page body.
|
||||
* Returned by landingPage() in the module export;
|
||||
* the shell wraps it with header, CSS, and analytics.
|
||||
*/
|
||||
export function renderLanding(): string {
|
||||
const demo = "https://demo.rspace.online/rdata";
|
||||
|
||||
return `
|
||||
<!-- Hero -->
|
||||
<section class="rl-hero">
|
||||
<span class="rl-tagline">rData</span>
|
||||
<h1 class="rl-heading">Privacy-First Analytics</h1>
|
||||
<p class="rl-subtext">
|
||||
See how your community engages without tracking individuals.
|
||||
No cookies, no consent banners, no third-party data — just
|
||||
clean, real-time metrics from your own server.
|
||||
</p>
|
||||
<div class="rl-cta-row">
|
||||
<a href="${demo}" class="rl-cta-primary" id="ml-primary">View Analytics</a>
|
||||
<a href="/create-space" class="rl-cta-secondary">Create a Space</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- How It Works -->
|
||||
<section class="rl-section">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center">How It Works</h2>
|
||||
<div class="rl-grid-3">
|
||||
<div class="rl-step">
|
||||
<div class="rl-step__num">1</div>
|
||||
<h3>Auto-Instrumented</h3>
|
||||
<p>Every rSpace module is automatically tracked — no code changes, no tag managers.</p>
|
||||
</div>
|
||||
<div class="rl-step">
|
||||
<div class="rl-step__num">2</div>
|
||||
<h3>View Dashboard</h3>
|
||||
<p>Open rData to see page views, active visitors, referrers, and top content in real time.</p>
|
||||
</div>
|
||||
<div class="rl-step">
|
||||
<div class="rl-step__num">3</div>
|
||||
<h3>Share Insights</h3>
|
||||
<p>Export stats or embed dashboard widgets in your canvas to keep the community informed.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Features -->
|
||||
<section class="rl-section rl-section--alt">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center">Features</h2>
|
||||
<div class="rl-grid-4">
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">🔒</div>
|
||||
<h3>No Cookies Required</h3>
|
||||
<p>Umami uses a privacy-friendly fingerprinting method. Zero cookies, zero local storage.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">✅</div>
|
||||
<h3>GDPR Compliant</h3>
|
||||
<p>No personal data collected. No consent banner needed. Compliant out of the box.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">⚡</div>
|
||||
<h3>Real-Time Dashboard</h3>
|
||||
<p>Active visitors, page views, bounce rate, and session duration update live.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">🦾</div>
|
||||
<h3>Lightweight Script</h3>
|
||||
<p>Under 2 KB tracker script. No impact on page load performance.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Why Self-Hosted -->
|
||||
<section class="rl-section">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center">Why Self-Hosted Analytics?</h2>
|
||||
<p class="rl-subtext" style="text-align:center">
|
||||
Compare rData with third-party analytics platforms.
|
||||
</p>
|
||||
<div class="rl-grid-3">
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">🚫</div>
|
||||
<h3>No Third-Party Data</h3>
|
||||
<p>Your visitor metrics never leave your server. No ad networks, no data brokers, no surprises.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">🚫</div>
|
||||
<h3>No Cookie Banners</h3>
|
||||
<p>Because no cookies are set, you skip the annoying consent popups entirely.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">🦿</div>
|
||||
<h3>Sub-2 KB Script</h3>
|
||||
<p>Google Analytics loads 45 KB+. The Umami tracker is under 2 KB — 20x lighter.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CTA -->
|
||||
<section class="rl-section rl-section--alt">
|
||||
<div class="rl-container" style="text-align:center">
|
||||
<h2 class="rl-heading">Analytics Without the Guilt</h2>
|
||||
<p class="rl-subtext">
|
||||
Respect your visitors. Understand your community. Keep your data.
|
||||
</p>
|
||||
<div class="rl-cta-row">
|
||||
<a href="${demo}" class="rl-cta-primary">View Analytics</a>
|
||||
<a href="/create-space" class="rl-cta-secondary">Create a Space</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="rl-back"><a href="/">← Back to rSpace</a></div>`;
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@ import { Hono } from "hono";
|
|||
import { renderShell } from "../../server/shell";
|
||||
import { getModuleInfoList } from "../../shared/module";
|
||||
import type { RSpaceModule } from "../../shared/module";
|
||||
import { renderLanding } from "./landing";
|
||||
|
||||
const routes = new Hono();
|
||||
|
||||
|
|
@ -139,6 +140,7 @@ export const dataModule: RSpaceModule = {
|
|||
description: "Privacy-first analytics for the r* ecosystem",
|
||||
routes,
|
||||
standaloneDomain: "rdata.online",
|
||||
landingPage: renderLanding,
|
||||
feeds: [
|
||||
{
|
||||
id: "analytics",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,94 @@
|
|||
/**
|
||||
* Files module landing page — rich content for rspace.online/rfiles
|
||||
*/
|
||||
|
||||
export function renderLanding(): string {
|
||||
return `
|
||||
<!-- Hero -->
|
||||
<div class="rl-hero">
|
||||
<span class="rl-tagline">rFiles</span>
|
||||
<h1 class="rl-heading">Share Files, Your Way</h1>
|
||||
<p class="rl-subtext">
|
||||
Upload, organize, and share with public links and memory cards.
|
||||
Built for communities who want control over their shared files.
|
||||
</p>
|
||||
<div class="rl-cta-row">
|
||||
<a href="https://demo.rspace.online/rfiles" class="rl-cta-primary" id="ml-primary">Start Sharing</a>
|
||||
<a href="/create-space" class="rl-cta-secondary">Create a Space</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- How it works -->
|
||||
<section class="rl-section">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center;">How It Works</h2>
|
||||
<div class="rl-grid-3">
|
||||
<div class="rl-step">
|
||||
<div class="rl-step__num">1</div>
|
||||
<h3>Upload Files</h3>
|
||||
<p>Drag and drop any file type. Images, documents, archives -- everything is welcome.</p>
|
||||
</div>
|
||||
<div class="rl-step">
|
||||
<div class="rl-step__num">2</div>
|
||||
<h3>Organize in Folders</h3>
|
||||
<p>Group files by project, topic, or team. Tag and search across your whole library.</p>
|
||||
</div>
|
||||
<div class="rl-step">
|
||||
<div class="rl-step__num">3</div>
|
||||
<h3>Share with Links</h3>
|
||||
<p>Generate public share links with optional expiration, download limits, and password protection.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Features -->
|
||||
<section class="rl-section rl-section--alt">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center;">Features</h2>
|
||||
<p class="rl-subtext" style="text-align:center;">
|
||||
Everything you need for community file management, without the surveillance.
|
||||
</p>
|
||||
<div class="rl-grid-4">
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">🔗</div>
|
||||
<h3>Public Share Links</h3>
|
||||
<p>Create expiring, password-protected, download-limited share links for any file.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">📄</div>
|
||||
<h3>Memory Cards</h3>
|
||||
<p>Attach structured metadata cards to files -- notes, context, and tags that travel with the content.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">📁</div>
|
||||
<h3>Folder Organization</h3>
|
||||
<p>Nest files in folders, tag freely, and search by name, type, or metadata across your library.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">🌐</div>
|
||||
<h3>Multi-Space Storage</h3>
|
||||
<p>Each community space gets its own file library. Share across spaces or keep things private.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CTA -->
|
||||
<section class="rl-section">
|
||||
<div class="rl-container" style="text-align:center;">
|
||||
<h2 class="rl-heading">Ready to share?</h2>
|
||||
<p class="rl-subtext">
|
||||
Try the demo or create your own space to start uploading.
|
||||
</p>
|
||||
<div class="rl-cta-row">
|
||||
<a href="https://demo.rspace.online/rfiles" class="rl-cta-primary">Start Sharing</a>
|
||||
<a href="/create-space" class="rl-cta-secondary">Create a Space</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="rl-back">
|
||||
<a href="/">← Back to rSpace</a>
|
||||
</div>`;
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ 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";
|
||||
|
||||
const routes = new Hono();
|
||||
|
||||
|
|
@ -384,6 +385,7 @@ export const filesModule: RSpaceModule = {
|
|||
icon: "\uD83D\uDCC1",
|
||||
description: "File sharing, share links, and memory cards",
|
||||
routes,
|
||||
landingPage: renderLanding,
|
||||
standaloneDomain: "rfiles.online",
|
||||
feeds: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,186 @@
|
|||
/**
|
||||
* Forum module landing page — static HTML, no React.
|
||||
*/
|
||||
export function renderLanding(): string {
|
||||
return `
|
||||
<!-- Hero -->
|
||||
<div class="rl-hero">
|
||||
<span class="rl-tagline" style="color:#818cf8;background:rgba(129,140,248,0.1);border-color:rgba(129,140,248,0.2)">rForum</span>
|
||||
<h1 class="rl-heading" style="background:linear-gradient(135deg,#a5b4fc,#c4b5fd,#d8b4fe);-webkit-background-clip:text;background-clip:text">
|
||||
Deploy Your Own Discourse Forum in Minutes
|
||||
</h1>
|
||||
<p class="rl-subtext">
|
||||
Automated cloud provisioning for self-hosted Discourse.
|
||||
No DevOps required. Choose your region, configure your settings, and go live.
|
||||
</p>
|
||||
<div class="rl-cta-row">
|
||||
<a href="https://demo.rspace.online/rforum" class="rl-cta-primary" id="ml-primary"
|
||||
style="background:linear-gradient(135deg,#4f46e5,#6d28d9)">Get Started</a>
|
||||
<a href="#pricing" class="rl-cta-secondary">View Pricing</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- How it Works -->
|
||||
<section class="rl-section">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center;background:linear-gradient(135deg,#a5b4fc,#c4b5fd);-webkit-background-clip:text;background-clip:text">How It Works</h2>
|
||||
<div class="rl-grid-3" style="margin-top:2rem">
|
||||
<div class="rl-card">
|
||||
<div class="rl-icon-box" style="background:rgba(99,102,241,0.12);color:#818cf8">
|
||||
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.066 2.573c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.573 1.066c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.066-2.573c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3>1. Configure</h3>
|
||||
<p>
|
||||
Choose your subdomain, server region, and instance size.
|
||||
Add SMTP credentials for email delivery. Set your admin email.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="rl-card">
|
||||
<div class="rl-icon-box" style="background:rgba(139,92,246,0.12);color:#a78bfa">
|
||||
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M5 12h14M12 5l7 7-7 7" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3>2. Provision</h3>
|
||||
<p>
|
||||
We create a cloud server on Hetzner, install Discourse,
|
||||
configure SSL via Let's Encrypt, and set up DNS. Takes about 10-15 minutes.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="rl-card">
|
||||
<div class="rl-icon-box" style="background:rgba(168,85,247,0.12);color:#c084fc">
|
||||
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M13 10V3L4 14h7v7l9-11h-7z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3>3. Go Live</h3>
|
||||
<p>
|
||||
Your forum is ready. Log in as admin, customize your community,
|
||||
invite members, and start conversations. Full SSH access included.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Pricing -->
|
||||
<section class="rl-section rl-section--alt" id="pricing">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center;background:linear-gradient(135deg,#a5b4fc,#c4b5fd);-webkit-background-clip:text;background-clip:text">
|
||||
Transparent Pricing
|
||||
</h2>
|
||||
<p class="rl-subtext" style="text-align:center">
|
||||
Pay only for the cloud server. Hetzner pricing passed through directly — no markup.
|
||||
</p>
|
||||
<div class="rl-grid-3" style="margin-top:2rem">
|
||||
<!-- Starter -->
|
||||
<div class="rl-card">
|
||||
<h3>Starter</h3>
|
||||
<p style="font-size:0.82rem;color:#94a3b8;margin-bottom:1rem">Small communities</p>
|
||||
<div style="font-size:2rem;font-weight:700;color:#e2e8f0;margin-bottom:0.25rem">
|
||||
€3.79<span style="font-size:0.875rem;font-weight:400;color:#94a3b8">/mo</span>
|
||||
</div>
|
||||
<p style="font-size:0.75rem;color:#64748b;margin-bottom:1.25rem">Hetzner CX22</p>
|
||||
<ul class="rl-check-list">
|
||||
<li>2 vCPU cores</li>
|
||||
<li>4 GB RAM</li>
|
||||
<li>40 GB NVMe SSD</li>
|
||||
<li>Up to ~500 users</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Standard -->
|
||||
<div class="rl-card" style="border-color:rgba(99,102,241,0.5)">
|
||||
<span class="rl-badge" style="background:#6366f1;margin-bottom:0.5rem">RECOMMENDED</span>
|
||||
<h3>Standard</h3>
|
||||
<p style="font-size:0.82rem;color:#94a3b8;margin-bottom:1rem">Growing communities</p>
|
||||
<div style="font-size:2rem;font-weight:700;color:#e2e8f0;margin-bottom:0.25rem">
|
||||
€6.80<span style="font-size:0.875rem;font-weight:400;color:#94a3b8">/mo</span>
|
||||
</div>
|
||||
<p style="font-size:0.75rem;color:#64748b;margin-bottom:1.25rem">Hetzner CX32</p>
|
||||
<ul class="rl-check-list">
|
||||
<li>4 vCPU cores</li>
|
||||
<li>8 GB RAM</li>
|
||||
<li>80 GB NVMe SSD</li>
|
||||
<li>Up to ~2,000 users</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Performance -->
|
||||
<div class="rl-card">
|
||||
<h3>Performance</h3>
|
||||
<p style="font-size:0.82rem;color:#94a3b8;margin-bottom:1rem">Large communities</p>
|
||||
<div style="font-size:2rem;font-weight:700;color:#e2e8f0;margin-bottom:0.25rem">
|
||||
€13.80<span style="font-size:0.875rem;font-weight:400;color:#94a3b8">/mo</span>
|
||||
</div>
|
||||
<p style="font-size:0.75rem;color:#64748b;margin-bottom:1.25rem">Hetzner CX42</p>
|
||||
<ul class="rl-check-list">
|
||||
<li>8 vCPU cores</li>
|
||||
<li>16 GB RAM</li>
|
||||
<li>160 GB NVMe SSD</li>
|
||||
<li>Up to ~10,000 users</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Features -->
|
||||
<section class="rl-section">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center;background:linear-gradient(135deg,#a5b4fc,#c4b5fd);-webkit-background-clip:text;background-clip:text">
|
||||
What You Get
|
||||
</h2>
|
||||
<div class="rl-grid-3" style="margin-top:2rem">
|
||||
<div class="rl-card">
|
||||
<h3>Automated SSL</h3>
|
||||
<p>Let's Encrypt certificates provisioned automatically during setup.</p>
|
||||
</div>
|
||||
<div class="rl-card">
|
||||
<h3>Multiple Regions</h3>
|
||||
<p>Deploy in Germany, Finland, or the US East/West Coast.</p>
|
||||
</div>
|
||||
<div class="rl-card">
|
||||
<h3>Full SSH Access</h3>
|
||||
<p>Your server, your rules. SSH in anytime for custom configuration.</p>
|
||||
</div>
|
||||
<div class="rl-card">
|
||||
<h3>One-Click Updates</h3>
|
||||
<p>Discourse's built-in admin panel handles version upgrades.</p>
|
||||
</div>
|
||||
<div class="rl-card">
|
||||
<h3>DNS Management</h3>
|
||||
<p>Automatic DNS setup for *.rforum.online subdomains.</p>
|
||||
</div>
|
||||
<div class="rl-card">
|
||||
<h3>Real-Time Logs</h3>
|
||||
<p>Watch your forum provision step-by-step in the dashboard.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CTA -->
|
||||
<section class="rl-section rl-section--alt">
|
||||
<div class="rl-container" style="text-align:center">
|
||||
<h2 class="rl-heading" style="background:linear-gradient(135deg,#a5b4fc,#c4b5fd);-webkit-background-clip:text;background-clip:text">
|
||||
Ready to launch your community?
|
||||
</h2>
|
||||
<p class="rl-subtext">
|
||||
Deploy a production Discourse forum in under 15 minutes. No DevOps experience needed.
|
||||
</p>
|
||||
<div class="rl-cta-row">
|
||||
<a href="https://demo.rspace.online/rforum" class="rl-cta-primary"
|
||||
style="background:linear-gradient(135deg,#4f46e5,#6d28d9)">Launch Your Forum</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="rl-back"><a href="/">← Back to rSpace</a></div>
|
||||
`;
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ import { getModuleInfoList } from "../../shared/module";
|
|||
import { provisionInstance, destroyInstance } from "./lib/provisioner";
|
||||
import type { RSpaceModule } from "../../shared/module";
|
||||
import { verifyEncryptIDToken, extractToken } from "@encryptid/sdk/server";
|
||||
import { renderLanding } from "./landing";
|
||||
|
||||
const routes = new Hono();
|
||||
|
||||
|
|
@ -175,6 +176,7 @@ export const forumModule: RSpaceModule = {
|
|||
icon: "\uD83D\uDCAC",
|
||||
description: "Deploy and manage Discourse forums",
|
||||
routes,
|
||||
landingPage: renderLanding,
|
||||
standaloneDomain: "rforum.online",
|
||||
feeds: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,117 @@
|
|||
/**
|
||||
* Funds module landing page — rich content for rspace.online/rfunds
|
||||
*/
|
||||
|
||||
export function renderLanding(): string {
|
||||
return `
|
||||
<!-- Hero -->
|
||||
<div class="rl-hero">
|
||||
<span class="rl-tagline">rFunds</span>
|
||||
<h1 class="rl-heading">Visualize Your Community's Money</h1>
|
||||
<p class="rl-subtext">
|
||||
Budget flows, river visualization, and conviction funding.
|
||||
Watch resources move through your community in real time.
|
||||
</p>
|
||||
<div class="rl-cta-row">
|
||||
<a href="https://demo.rspace.online/rfunds" class="rl-cta-primary" id="ml-primary">See Your Funds Flow</a>
|
||||
<a href="/create-space" class="rl-cta-secondary">Create a Space</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- How it works -->
|
||||
<section class="rl-section">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center;">How It Works</h2>
|
||||
<div class="rl-grid-3">
|
||||
<div class="rl-step">
|
||||
<div class="rl-step__num">1</div>
|
||||
<h3>Define Budget Streams</h3>
|
||||
<p>Create named flows between funding pools. Set rates, thresholds, and allocation rules.</p>
|
||||
</div>
|
||||
<div class="rl-step">
|
||||
<div class="rl-step__num">2</div>
|
||||
<h3>Watch Money Flow</h3>
|
||||
<p>The river visualization animates budget movement in real time -- deposits, withdrawals, and funnels.</p>
|
||||
</div>
|
||||
<div class="rl-step">
|
||||
<div class="rl-step__num">3</div>
|
||||
<h3>Govern Allocation</h3>
|
||||
<p>Community members signal preferences through conviction funding. Resources flow where attention goes.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Features -->
|
||||
<section class="rl-section rl-section--alt">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center;">Features</h2>
|
||||
<p class="rl-subtext" style="text-align:center;">
|
||||
Transparent treasury management for communities of any size.
|
||||
</p>
|
||||
<div class="rl-grid-4">
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">🌊</div>
|
||||
<h3>River Visualization</h3>
|
||||
<p>Animated flow diagram showing how funds move between pools, funnels, and outcomes in real time.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">💡</div>
|
||||
<h3>Conviction Funding</h3>
|
||||
<p>Continuous signaling lets community members express preferences that compound over time.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">💨</div>
|
||||
<h3>Budget Streams</h3>
|
||||
<p>Named flows between funding pools with configurable rates, thresholds, and activation rules.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">📊</div>
|
||||
<h3>Treasury Dashboard</h3>
|
||||
<p>Full overview of balances, inflows, outflows, and historical transaction data for your community.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Integration -->
|
||||
<section class="rl-section">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center;">Ecosystem Integration</h2>
|
||||
<div class="rl-grid-2" style="max-width:700px;margin:0 auto;">
|
||||
<div class="rl-integration">
|
||||
<div class="rl-icon-box">💰</div>
|
||||
<div>
|
||||
<h3>rWallet</h3>
|
||||
<p>Connects to <a href="/rwallet" style="color:#14b8a6;">rWallet</a> for on-chain balances and wallet-based treasury tracking.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rl-integration">
|
||||
<div class="rl-icon-box">🗳</div>
|
||||
<div>
|
||||
<h3>rVote</h3>
|
||||
<p>Pairs with <a href="/rvote" style="color:#14b8a6;">rVote</a> for governance decisions that direct fund allocation.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CTA -->
|
||||
<section class="rl-section rl-section--alt">
|
||||
<div class="rl-container" style="text-align:center;">
|
||||
<h2 class="rl-heading">See your community's funds in motion</h2>
|
||||
<p class="rl-subtext">
|
||||
Try the demo or create a space to set up your own budget flows.
|
||||
</p>
|
||||
<div class="rl-cta-row">
|
||||
<a href="https://demo.rspace.online/rfunds" class="rl-cta-primary">See Your Funds Flow</a>
|
||||
<a href="/create-space" class="rl-cta-secondary">Create a Space</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="rl-back">
|
||||
<a href="/">← Back to rSpace</a>
|
||||
</div>`;
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ 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";
|
||||
|
||||
const FLOW_SERVICE_URL = process.env.FLOW_SERVICE_URL || "http://payment-flow:3010";
|
||||
|
||||
|
|
@ -245,6 +246,7 @@ export const fundsModule: RSpaceModule = {
|
|||
icon: "\uD83C\uDF0A",
|
||||
description: "Budget flows, river visualization, and treasury management",
|
||||
routes,
|
||||
landingPage: renderLanding,
|
||||
standaloneDomain: "rfunds.online",
|
||||
feeds: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,153 @@
|
|||
/**
|
||||
* Inbox module landing page — static HTML, no React.
|
||||
*/
|
||||
export function renderLanding(): string {
|
||||
return `
|
||||
<!-- Hero -->
|
||||
<div class="rl-hero">
|
||||
<span class="rl-tagline" style="color:#06b6d4;background:rgba(6,182,212,0.1);border-color:rgba(6,182,212,0.2)">rInbox</span>
|
||||
<h1 class="rl-heading" style="background:linear-gradient(135deg,#67e8f9,#93c5fd,#c4b5fd);-webkit-background-clip:text;background-clip:text">
|
||||
Collaborative Multi-Sig Inbox
|
||||
</h1>
|
||||
<p class="rl-subtext">
|
||||
A shared email client where teams read, discuss, and approve messages
|
||||
together — with cryptographic multi-signature workflows before anything gets sent.
|
||||
</p>
|
||||
<div class="rl-cta-row">
|
||||
<a href="https://demo.rspace.online/rinbox" class="rl-cta-primary" id="ml-primary"
|
||||
style="background:linear-gradient(135deg,#0891b2,#0e7490)">Open Inbox</a>
|
||||
<a href="/create-space" class="rl-cta-secondary">Create a Space</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- How it Works -->
|
||||
<section class="rl-section">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center;background:linear-gradient(135deg,#67e8f9,#93c5fd);-webkit-background-clip:text;background-clip:text">How It Works</h2>
|
||||
<div class="rl-grid-3" style="margin-top:2rem">
|
||||
<div class="rl-card">
|
||||
<div class="rl-icon-box" style="background:rgba(6,182,212,0.12);color:#06b6d4">
|
||||
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3>1. Connect Mailbox</h3>
|
||||
<p>
|
||||
Your team shares one real email address. Every member sees every thread
|
||||
in real-time — no forwarding, no BCC chains, no lost context.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="rl-card">
|
||||
<div class="rl-icon-box" style="background:rgba(59,130,246,0.12);color:#3b82f6">
|
||||
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M17 8h2a2 2 0 012 2v6a2 2 0 01-2 2h-2v4l-4-4H9a1.994 1.994 0 01-1.414-.586m0 0L11 14h4a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2v4l.586-.586z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3>2. Triage Together</h3>
|
||||
<p>
|
||||
Comment on any thread privately before replying. Tag members,
|
||||
discuss strategy, and reach consensus — all alongside the original message.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="rl-card">
|
||||
<div class="rl-icon-box" style="background:rgba(139,92,246,0.12);color:#8b5cf6">
|
||||
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3>3. Approve with Multi-Sig</h3>
|
||||
<p>
|
||||
Outbound emails require M-of-N cryptographic signatures before sending.
|
||||
Every approval is auditable — no rogue replies.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Collaboration Features -->
|
||||
<section class="rl-section rl-section--alt">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center;background:linear-gradient(135deg,#67e8f9,#93c5fd);-webkit-background-clip:text;background-clip:text">
|
||||
Collaboration Features
|
||||
</h2>
|
||||
<p class="rl-subtext" style="text-align:center">Built for collective intelligence, not individual productivity.</p>
|
||||
<div class="rl-grid-2" style="margin-top:2rem">
|
||||
<div class="rl-card">
|
||||
<h3 style="color:#06b6d4;margin-bottom:0.75rem">Real-Time Collaboration</h3>
|
||||
<ul class="rl-check-list">
|
||||
<li><strong>Threaded Comments</strong> — discuss any email privately before replying</li>
|
||||
<li><strong>Draft Reviews</strong> — collaborative draft editing with inline suggestions</li>
|
||||
<li><strong>Forwarding Rules</strong> — route incoming messages by sender, subject, or tag</li>
|
||||
<li><strong>Inline Signatures</strong> — per-mailbox signature templates with merge fields</li>
|
||||
<li><strong>Read Receipts</strong> — see who has read each thread and when</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="rl-card">
|
||||
<h3 style="color:#8b5cf6;margin-bottom:0.75rem">Approval Workflows</h3>
|
||||
<ul class="rl-check-list">
|
||||
<li><strong>Multi-sig sending</strong> — outbound emails require M-of-N approval</li>
|
||||
<li><strong>Configurable thresholds</strong> — set approval requirements per mailbox</li>
|
||||
<li><strong>Audit trail</strong> — every approval, rejection, and edit is logged</li>
|
||||
<li><strong>Role-based access</strong> — Admin, Reviewer, Approver, Viewer, Bot</li>
|
||||
<li><strong>Deadline alerts</strong> — notifications when approvals are waiting</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Team Roles -->
|
||||
<section class="rl-section">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center;background:linear-gradient(135deg,#67e8f9,#93c5fd);-webkit-background-clip:text;background-clip:text">
|
||||
Team Roles
|
||||
</h2>
|
||||
<p class="rl-subtext" style="text-align:center">Fine-grained access control for every team member.</p>
|
||||
<div style="display:grid;grid-template-columns:repeat(5,1fr);gap:0.75rem;margin-top:2rem">
|
||||
<div class="rl-card rl-card--center" style="padding:1.25rem">
|
||||
<p style="font-size:0.875rem;font-weight:600;color:#06b6d4">Admin</p>
|
||||
<p style="font-size:0.75rem;color:#64748b;margin-top:0.35rem">Full control, manage members</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center" style="padding:1.25rem">
|
||||
<p style="font-size:0.875rem;font-weight:600;color:#3b82f6">Reviewer</p>
|
||||
<p style="font-size:0.75rem;color:#64748b;margin-top:0.35rem">Read, comment, suggest edits</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center" style="padding:1.25rem">
|
||||
<p style="font-size:0.875rem;font-weight:600;color:#8b5cf6">Approver</p>
|
||||
<p style="font-size:0.75rem;color:#64748b;margin-top:0.35rem">Sign outbound emails</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center" style="padding:1.25rem">
|
||||
<p style="font-size:0.875rem;font-weight:600;color:#94a3b8">Viewer</p>
|
||||
<p style="font-size:0.75rem;color:#64748b;margin-top:0.35rem">Read-only access</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center" style="padding:1.25rem">
|
||||
<p style="font-size:0.875rem;font-weight:600;color:#94a3b8">Bot</p>
|
||||
<p style="font-size:0.75rem;color:#64748b;margin-top:0.35rem">Automated integrations</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CTA -->
|
||||
<section class="rl-section rl-section--alt">
|
||||
<div class="rl-container" style="text-align:center">
|
||||
<h2 class="rl-heading" style="background:linear-gradient(135deg,#67e8f9,#93c5fd);-webkit-background-clip:text;background-clip:text">
|
||||
Start collaborating on email
|
||||
</h2>
|
||||
<p class="rl-subtext">
|
||||
Sign in with your passkey to join a shared inbox.
|
||||
</p>
|
||||
<div class="rl-cta-row">
|
||||
<a href="https://demo.rspace.online/rinbox" class="rl-cta-primary"
|
||||
style="background:linear-gradient(135deg,#0891b2,#0e7490)">Open Inbox</a>
|
||||
<a href="/create-space" class="rl-cta-secondary">Create a Space</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="rl-back"><a href="/">← Back to rSpace</a></div>
|
||||
`;
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ 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";
|
||||
|
||||
const routes = new Hono();
|
||||
|
||||
|
|
@ -599,6 +600,7 @@ export const inboxModule: RSpaceModule = {
|
|||
icon: "\u{1F4E8}",
|
||||
description: "Collaborative email with multisig approval",
|
||||
routes,
|
||||
landingPage: renderLanding,
|
||||
standaloneDomain: "rinbox.online",
|
||||
feeds: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,320 @@
|
|||
/**
|
||||
* Maps module landing page — static HTML, no React.
|
||||
*/
|
||||
export function renderLanding(): string {
|
||||
return `
|
||||
<!-- Hero -->
|
||||
<div class="rl-hero">
|
||||
<span class="rl-tagline" style="color:#10b981;background:rgba(16,185,129,0.1);border-color:rgba(16,185,129,0.2)">rMaps</span>
|
||||
<h1 class="rl-heading" style="background:linear-gradient(135deg,#6ee7b7,#2dd4bf,#67e8f9);-webkit-background-clip:text;background-clip:text">
|
||||
Real-Time Collaborative Maps
|
||||
</h1>
|
||||
<p class="rl-subtext">
|
||||
Share live locations, navigate indoor and outdoor spaces, coordinate meetups
|
||||
— all from the browser. No app install. No tracking. No data collection.
|
||||
</p>
|
||||
<div class="rl-cta-row">
|
||||
<a href="https://demo.rspace.online/rmaps" class="rl-cta-primary" id="ml-primary"
|
||||
style="background:linear-gradient(135deg,#059669,#0d9488)">Try the Demo</a>
|
||||
<a href="/create-space" class="rl-cta-secondary">Create a Room</a>
|
||||
</div>
|
||||
<p style="font-size:0.82rem;color:#64748b;margin-top:1rem">No sign-up required to join. Works on any device.</p>
|
||||
</div>
|
||||
|
||||
<!-- Core Features -->
|
||||
<section class="rl-section">
|
||||
<div class="rl-container">
|
||||
<span class="rl-tagline" style="color:#10b981;background:rgba(16,185,129,0.1);border-color:rgba(16,185,129,0.2);display:block;text-align:center;margin:0 auto 1rem">Core Features</span>
|
||||
<h2 class="rl-heading" style="text-align:center;background:linear-gradient(135deg,#6ee7b7,#2dd4bf);-webkit-background-clip:text;background-clip:text">
|
||||
Everything you need to find your friends
|
||||
</h2>
|
||||
<div class="rl-grid-4" style="margin-top:2rem">
|
||||
<div class="rl-card">
|
||||
<div class="rl-icon-box" style="background:rgba(16,185,129,0.12);color:#10b981">
|
||||
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15 10.5a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 10.5c0 7.142-7.5 11.25-7.5 11.25S4.5 17.642 4.5 10.5a7.5 7.5 0 1115 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3>Live GPS Sharing</h3>
|
||||
<p>
|
||||
Real-time location updates via WebSocket. See everyone on the map
|
||||
as they move, with stale detection and high-accuracy fallback.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="rl-card">
|
||||
<div class="rl-icon-box" style="background:rgba(6,182,212,0.12);color:#06b6d4">
|
||||
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 6.75V15m6-6v8.25m.503 3.498l4.875-2.437c.381-.19.622-.58.622-1.006V4.82c0-.836-.88-1.38-1.628-1.006l-3.869 1.934c-.317.159-.69.159-1.006 0L9.503 3.252a1.125 1.125 0 00-1.006 0L3.622 5.689C3.24 5.88 3 6.27 3 6.695V19.18c0 .836.88 1.38 1.628 1.006l3.869-1.934c.317-.159.69-.159 1.006 0l4.994 2.497c.317.158.69.158 1.006 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3>Indoor + Outdoor Nav</h3>
|
||||
<p>
|
||||
Turn-by-turn routing via OSRM outdoors, seamless switch to c3nav
|
||||
for indoor venues. Multi-floor, level-aware navigation.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="rl-card">
|
||||
<div class="rl-icon-box" style="background:rgba(99,102,241,0.12);color:#6366f1">
|
||||
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3 3v1.5M3 21v-6m0 0l2.77-.693a9 9 0 016.208.682l.108.054a9 9 0 006.086.71l3.114-.732a48.524 48.524 0 01-.005-10.499l-3.11.732a9 9 0 01-6.085-.711l-.108-.054a9 9 0 00-6.208-.682L3 4.5M3 15V4.5" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3>Meeting Points</h3>
|
||||
<p>
|
||||
Drop waypoints for meetups, events, and points of interest.
|
||||
Search by address, share coordinates, or pin from your location.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="rl-card">
|
||||
<div class="rl-icon-box" style="background:rgba(244,63,94,0.12);color:#f43f5e">
|
||||
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75L11.25 15 15 9.75m-3-7.036A11.959 11.959 0 013.598 6 11.99 11.99 0 003 9.749c0 5.592 3.824 10.29 9 11.623 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.571-.598-3.751h-.152c-3.196 0-6.1-1.248-8.25-3.285z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3>Privacy First</h3>
|
||||
<p>
|
||||
Ghost mode, precision levels, one-toggle location sharing.
|
||||
Zero tracking, zero data collection. You control who sees you.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- What Makes rMaps Different -->
|
||||
<section class="rl-section rl-section--alt">
|
||||
<div class="rl-container">
|
||||
<span class="rl-tagline" style="color:#2dd4bf;background:rgba(45,212,191,0.1);border-color:rgba(45,212,191,0.2);display:block;text-align:center;margin:0 auto 1rem">Beyond Google Maps</span>
|
||||
<h2 class="rl-heading" style="text-align:center;background:linear-gradient(135deg,#6ee7b7,#2dd4bf);-webkit-background-clip:text;background-clip:text">
|
||||
What makes rMaps different
|
||||
</h2>
|
||||
<div class="rl-grid-2" style="margin-top:2rem">
|
||||
<div class="rl-integration">
|
||||
<div class="rl-icon-box" style="background:rgba(16,185,129,0.12);color:#10b981;font-size:1.25rem;flex-shrink:0">
|
||||
🏕️
|
||||
</div>
|
||||
<div>
|
||||
<h3>CCC Event Integration</h3>
|
||||
<p>
|
||||
Native c3nav integration for 39C3, Camp, and other CCC events. Indoor maps with
|
||||
multi-floor routing, venue bounds detection, and automatic map switching
|
||||
when you walk inside.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rl-integration">
|
||||
<div class="rl-icon-box" style="background:rgba(6,182,212,0.12);color:#06b6d4;font-size:1.25rem;flex-shrink:0">
|
||||
📡
|
||||
</div>
|
||||
<div>
|
||||
<h3>Location Pinging</h3>
|
||||
<p>
|
||||
Request a friend's location with one tap. Push notifications via
|
||||
Web Push API with vibration alerts. Works even when the app is
|
||||
backgrounded via service worker.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rl-integration">
|
||||
<div class="rl-icon-box" style="background:rgba(99,102,241,0.12);color:#6366f1;font-size:1.25rem;flex-shrink:0">
|
||||
📦
|
||||
</div>
|
||||
<div>
|
||||
<h3>Google Maps Import</h3>
|
||||
<p>
|
||||
Import your saved places from Google Takeout ZIP exports. GeoJSON
|
||||
parsing, auto-emoji mapping by place type, and preview before
|
||||
importing.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rl-integration">
|
||||
<div class="rl-icon-box" style="background:rgba(245,158,11,0.12);color:#f59e0b;font-size:1.25rem;flex-shrink:0">
|
||||
📱
|
||||
</div>
|
||||
<div>
|
||||
<h3>PWA & Offline Mode</h3>
|
||||
<p>
|
||||
Install as a native app. Three-tier service worker caching keeps
|
||||
maps accessible offline with up to 500 cached tiles. Background
|
||||
sync handles location updates.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rl-integration">
|
||||
<div class="rl-icon-box" style="background:rgba(139,92,246,0.12);color:#8b5cf6;font-size:1.25rem;flex-shrink:0">
|
||||
🔗
|
||||
</div>
|
||||
<div>
|
||||
<h3>Instant Room Sharing</h3>
|
||||
<p>
|
||||
Generate a QR code or shareable link for any room. Friends scan or tap
|
||||
to join instantly — no account needed, no app download.
|
||||
Native share dialog on mobile.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rl-integration">
|
||||
<div class="rl-icon-box" style="background:rgba(244,63,94,0.12);color:#f43f5e;font-size:1.25rem;flex-shrink:0">
|
||||
🔄
|
||||
</div>
|
||||
<div>
|
||||
<h3>Conflict-Free Sync</h3>
|
||||
<p>
|
||||
Automerge CRDT architecture ensures everyone sees the same map state,
|
||||
even through disconnections. WebSocket real-time sync with automatic
|
||||
reconnection and state recovery.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- How it Works -->
|
||||
<section class="rl-section">
|
||||
<div class="rl-container">
|
||||
<span class="rl-tagline" style="color:#06b6d4;background:rgba(6,182,212,0.1);border-color:rgba(6,182,212,0.2);display:block;text-align:center;margin:0 auto 1rem">How It Works</span>
|
||||
<h2 class="rl-heading" style="text-align:center;background:linear-gradient(135deg,#6ee7b7,#2dd4bf);-webkit-background-clip:text;background-clip:text">
|
||||
Three steps to find your crew
|
||||
</h2>
|
||||
<div class="rl-grid-3" style="margin-top:2rem">
|
||||
<div class="rl-step">
|
||||
<div class="rl-step__num" style="background:rgba(16,185,129,0.15);color:#10b981">1</div>
|
||||
<h3>Create a Map Room</h3>
|
||||
<p>
|
||||
Sign in and name your room. Get a shareable link or a custom slug.
|
||||
</p>
|
||||
</div>
|
||||
<div class="rl-step">
|
||||
<div class="rl-step__num" style="background:rgba(6,182,212,0.15);color:#06b6d4">2</div>
|
||||
<h3>Share with Friends</h3>
|
||||
<p>
|
||||
Send the link or scan the QR code. Friends join from any browser
|
||||
— no app download, no account creation needed.
|
||||
</p>
|
||||
</div>
|
||||
<div class="rl-step">
|
||||
<div class="rl-step__num" style="background:rgba(99,102,241,0.15);color:#6366f1">3</div>
|
||||
<h3>Navigate Together</h3>
|
||||
<p>
|
||||
See everyone in real time. Drop meeting points, get turn-by-turn
|
||||
directions, and ping friends when you need to regroup.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Use Cases -->
|
||||
<section class="rl-section rl-section--alt">
|
||||
<div class="rl-container">
|
||||
<span class="rl-tagline" style="color:#6366f1;background:rgba(99,102,241,0.1);border-color:rgba(99,102,241,0.2);display:block;text-align:center;margin:0 auto 1rem">Built For</span>
|
||||
<h2 class="rl-heading" style="text-align:center;background:linear-gradient(135deg,#6ee7b7,#2dd4bf);-webkit-background-clip:text;background-clip:text">
|
||||
Maps for every gathering
|
||||
</h2>
|
||||
<div class="rl-grid-3" style="margin-top:2rem">
|
||||
<div class="rl-card rl-card--center">
|
||||
<div style="font-size:2rem;margin-bottom:0.75rem">🏕️</div>
|
||||
<h3>Festivals & Camps</h3>
|
||||
<p>
|
||||
Navigate massive venues with indoor maps. Find stages, food courts,
|
||||
and your crew across multi-day events like CCC Camp.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="rl-card rl-card--center">
|
||||
<div style="font-size:2rem;margin-bottom:0.75rem">🏙️</div>
|
||||
<h3>City Exploration</h3>
|
||||
<p>
|
||||
Exploring a new city with friends? Share locations, drop pins at
|
||||
restaurants and landmarks, import your Google Maps saved places.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="rl-card rl-card--center">
|
||||
<div style="font-size:2rem;margin-bottom:0.75rem">🤝</div>
|
||||
<h3>Group Coordination</h3>
|
||||
<p>
|
||||
Conferences, retreats, team offsites. Set meeting points, ping
|
||||
stragglers, and get walking directions to the next session.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<p style="text-align:center;font-size:0.82rem;color:#64748b;margin-top:1.5rem">
|
||||
First built for <a href="https://events.ccc.de/" target="_blank" rel="noopener noreferrer" style="color:rgba(16,185,129,0.7)">CCC events</a> — now for any gathering.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Technical Highlights -->
|
||||
<section class="rl-section">
|
||||
<div class="rl-container">
|
||||
<span class="rl-tagline" style="color:#94a3b8;background:rgba(100,116,139,0.1);border-color:rgba(100,116,139,0.2);display:block;text-align:center;margin:0 auto 1rem">Under the Hood</span>
|
||||
<h2 class="rl-heading" style="text-align:center;background:linear-gradient(135deg,#6ee7b7,#2dd4bf);-webkit-background-clip:text;background-clip:text">
|
||||
Built on open standards
|
||||
</h2>
|
||||
<div class="rl-grid-4" style="margin-top:2rem">
|
||||
<div class="rl-card rl-card--center" style="padding:1rem">
|
||||
<p style="font-size:0.875rem;font-weight:600;color:#e2e8f0">MapLibre GL</p>
|
||||
<p style="font-size:0.75rem;color:#64748b;margin-top:0.25rem">Open-source maps</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center" style="padding:1rem">
|
||||
<p style="font-size:0.875rem;font-weight:600;color:#e2e8f0">OSRM</p>
|
||||
<p style="font-size:0.75rem;color:#64748b;margin-top:0.25rem">Outdoor routing</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center" style="padding:1rem">
|
||||
<p style="font-size:0.875rem;font-weight:600;color:#e2e8f0">c3nav</p>
|
||||
<p style="font-size:0.75rem;color:#64748b;margin-top:0.25rem">Indoor navigation</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center" style="padding:1rem">
|
||||
<p style="font-size:0.875rem;font-weight:600;color:#e2e8f0">Automerge</p>
|
||||
<p style="font-size:0.75rem;color:#64748b;margin-top:0.25rem">CRDT sync</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center" style="padding:1rem">
|
||||
<p style="font-size:0.875rem;font-weight:600;color:#e2e8f0">Web Push</p>
|
||||
<p style="font-size:0.75rem;color:#64748b;margin-top:0.25rem">Notifications</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center" style="padding:1rem">
|
||||
<p style="font-size:0.875rem;font-weight:600;color:#e2e8f0">Service Worker</p>
|
||||
<p style="font-size:0.75rem;color:#64748b;margin-top:0.25rem">Offline & PWA</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center" style="padding:1rem">
|
||||
<p style="font-size:0.875rem;font-weight:600;color:#e2e8f0">WebSocket</p>
|
||||
<p style="font-size:0.75rem;color:#64748b;margin-top:0.25rem">Real-time sync</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center" style="padding:1rem">
|
||||
<p style="font-size:0.875rem;font-weight:600;color:#e2e8f0">EncryptID</p>
|
||||
<p style="font-size:0.75rem;color:#64748b;margin-top:0.25rem">Identity & auth</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CTA -->
|
||||
<section class="rl-section rl-section--alt">
|
||||
<div class="rl-container" style="text-align:center">
|
||||
<h2 class="rl-heading" style="background:linear-gradient(135deg,#6ee7b7,#2dd4bf);-webkit-background-clip:text;background-clip:text">
|
||||
Ready to find your crew?
|
||||
</h2>
|
||||
<p class="rl-subtext">Create a map room and share the link. That's it.</p>
|
||||
<div class="rl-cta-row">
|
||||
<a href="https://demo.rspace.online/rmaps" class="rl-cta-primary"
|
||||
style="background:linear-gradient(135deg,#059669,#0d9488)">Try the Demo</a>
|
||||
<a href="/create-space" class="rl-cta-secondary">Create a Space</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="rl-back"><a href="/">← Back to rSpace</a></div>
|
||||
`;
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ import { Hono } from "hono";
|
|||
import { renderShell } from "../../server/shell";
|
||||
import { getModuleInfoList } from "../../shared/module";
|
||||
import type { RSpaceModule } from "../../shared/module";
|
||||
import { renderLanding } from "./landing";
|
||||
|
||||
const routes = new Hono();
|
||||
|
||||
|
|
@ -167,6 +168,7 @@ export const mapsModule: RSpaceModule = {
|
|||
icon: "\u{1F5FA}",
|
||||
description: "Real-time collaborative location sharing and indoor/outdoor maps",
|
||||
routes,
|
||||
landingPage: renderLanding,
|
||||
standaloneDomain: "rmaps.online",
|
||||
feeds: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,183 @@
|
|||
/**
|
||||
* Notes module landing page — static HTML, no React.
|
||||
*/
|
||||
export function renderLanding(): string {
|
||||
return `
|
||||
<!-- Hero -->
|
||||
<div class="rl-hero">
|
||||
<span class="rl-tagline" style="color:#f59e0b;background:rgba(245,158,11,0.1);border-color:rgba(245,158,11,0.2)">rNotes</span>
|
||||
<h1 class="rl-heading" style="background:linear-gradient(135deg,#f59e0b,#f97316);-webkit-background-clip:text;background-clip:text">
|
||||
Capture Everything, Find Anything, and Share your Insights
|
||||
</h1>
|
||||
<p class="rl-subtext">
|
||||
Notes, clips, voice recordings, and live transcription — all in one place.
|
||||
Speak and watch your words appear in real time, or drop in audio and video files to transcribe offline.
|
||||
</p>
|
||||
<div class="rl-cta-row">
|
||||
<a href="https://demo.rspace.online/rnotes" class="rl-cta-primary" id="ml-primary"
|
||||
style="background:linear-gradient(135deg,#f59e0b,#d97706)">Open Notebook</a>
|
||||
<a href="https://demo.rspace.online/rnotes" class="rl-cta-secondary"
|
||||
style="border-color:rgba(34,197,94,0.3);color:#4ade80">Unlock Article</a>
|
||||
<a href="https://demo.rspace.online/rnotes" class="rl-cta-secondary"
|
||||
style="border-color:rgba(139,92,246,0.3);color:#a78bfa">Transcribe</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- How it works -->
|
||||
<section class="rl-section">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center;background:linear-gradient(135deg,#f59e0b,#f97316);-webkit-background-clip:text;background-clip:text">How It Works</h2>
|
||||
<div class="rl-grid-4">
|
||||
<div class="rl-step">
|
||||
<div class="rl-step__num" style="background:rgba(245,158,11,0.1);color:#f59e0b">1</div>
|
||||
<h3>Live Transcribe</h3>
|
||||
<p>Speak and watch your words appear in real time. WebSocket streaming with live timestamps.</p>
|
||||
</div>
|
||||
<div class="rl-step">
|
||||
<div class="rl-step__num" style="background:rgba(245,158,11,0.1);color:#f59e0b">2</div>
|
||||
<h3>Audio & Video</h3>
|
||||
<p>Drop in audio or video files and get a full transcript. Powered by NVIDIA Parakeet — runs entirely in your browser.</p>
|
||||
</div>
|
||||
<div class="rl-step">
|
||||
<div class="rl-step__num" style="background:rgba(245,158,11,0.1);color:#f59e0b">3</div>
|
||||
<h3>Notebooks & Tags</h3>
|
||||
<p>Organize transcripts into notebooks alongside notes, clips, code, and files. Tag freely, search everything.</p>
|
||||
</div>
|
||||
<div class="rl-step">
|
||||
<div class="rl-step__num" style="background:rgba(245,158,11,0.1);color:#f59e0b">4</div>
|
||||
<h3>Private & Offline</h3>
|
||||
<p>Parakeet.js runs entirely in the browser. Your audio never leaves your device — full offline support.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Memory Cards -->
|
||||
<section class="rl-section rl-section--alt">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center;background:linear-gradient(135deg,#f59e0b,#f97316);-webkit-background-clip:text;background-clip:text">Memory Cards</h2>
|
||||
<p class="rl-subtext" style="text-align:center">
|
||||
Every note is a Memory Card — a typed, structured unit of knowledge with hierarchy,
|
||||
properties, and attachments. Designed for round-trip interoperability with Logseq.
|
||||
</p>
|
||||
<div class="rl-grid-3">
|
||||
<!-- 7 Card Types -->
|
||||
<div class="rl-card">
|
||||
<div class="rl-icon-box" style="background:rgba(245,158,11,0.12);color:#f59e0b">
|
||||
<svg width="20" height="20" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3>7 Card Types</h3>
|
||||
<div style="display:flex;flex-wrap:wrap;gap:0.35rem;margin-bottom:0.75rem">
|
||||
<span class="rl-badge" style="background:rgba(245,158,11,0.2);color:#f59e0b">note</span>
|
||||
<span class="rl-badge" style="background:rgba(59,130,246,0.2);color:#3b82f6">link</span>
|
||||
<span class="rl-badge" style="background:rgba(34,197,94,0.2);color:#22c55e">task</span>
|
||||
<span class="rl-badge" style="background:rgba(234,179,8,0.2);color:#eab308">idea</span>
|
||||
<span class="rl-badge" style="background:rgba(168,85,247,0.2);color:#a855f7">person</span>
|
||||
<span class="rl-badge" style="background:rgba(236,72,153,0.2);color:#ec4899">reference</span>
|
||||
<span class="rl-badge" style="background:rgba(100,116,139,0.2);color:#94a3b8">file</span>
|
||||
</div>
|
||||
<p>Each card type has distinct styling and behavior. Typed notes surface in filtered views and canvas visualizations.</p>
|
||||
</div>
|
||||
|
||||
<!-- Hierarchy & Properties -->
|
||||
<div class="rl-card">
|
||||
<div class="rl-icon-box" style="background:rgba(245,158,11,0.12);color:#f59e0b">
|
||||
<svg width="20" height="20" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M4 6h16M4 10h16M4 14h16M4 18h16" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3>Hierarchy & Properties</h3>
|
||||
<p>
|
||||
Nest cards under parents to build knowledge trees. Add structured
|
||||
<code style="font-size:0.8rem;color:rgba(245,158,11,0.8)">key:: value</code>
|
||||
properties — compatible with Logseq's property syntax.
|
||||
</p>
|
||||
<div style="margin-top:0.5rem;font-family:monospace;font-size:0.75rem;color:#64748b;line-height:1.7">
|
||||
<div><span style="color:#94a3b8">type::</span> idea</div>
|
||||
<div><span style="color:#94a3b8">status::</span> doing</div>
|
||||
<div><span style="color:#94a3b8">tags::</span> #research, #web3</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Logseq Import/Export -->
|
||||
<div class="rl-card">
|
||||
<div class="rl-icon-box" style="background:rgba(245,158,11,0.12);color:#f59e0b">
|
||||
<svg width="20" height="20" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3>Logseq Import & Export</h3>
|
||||
<p>
|
||||
Export your notebooks as Logseq-compatible ZIP archives. Import a Logseq graph and keep your pages,
|
||||
properties, tags, and hierarchy intact.
|
||||
</p>
|
||||
<p style="font-size:0.75rem;color:#64748b;margin-top:0.5rem">
|
||||
Round-trip fidelity: card types, tags, attachments, and parent-child structure all survive the journey.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Dual Format Storage -->
|
||||
<div class="rl-card">
|
||||
<div class="rl-icon-box" style="background:rgba(245,158,11,0.12);color:#f59e0b">
|
||||
<svg width="20" height="20" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3>Dual Format Storage</h3>
|
||||
<p>
|
||||
Every card stores rich TipTap JSON for editing and portable Markdown for search, export, and interoperability.
|
||||
Write once, read anywhere.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Structured Attachments -->
|
||||
<div class="rl-card">
|
||||
<div class="rl-icon-box" style="background:rgba(245,158,11,0.12);color:#f59e0b">
|
||||
<svg width="20" height="20" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15.172 7l-6.586 6.586a2 2 0 102.828 2.828l6.414-6.586a4 4 0 00-5.656-5.656l-6.415 6.585a6 6 0 108.486 8.486L20.5 13" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3>Structured Attachments</h3>
|
||||
<p>
|
||||
Attach images, PDFs, audio, and files to any card with roles (primary, preview, supporting) and captions.
|
||||
Thumbnails render inline.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- FUN Model -->
|
||||
<div class="rl-card">
|
||||
<div class="rl-icon-box" style="background:rgba(245,158,11,0.12);color:#f59e0b">
|
||||
<svg width="20" height="20" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3>FUN, Not CRUD</h3>
|
||||
<p>
|
||||
<span style="color:#f59e0b;font-weight:600">F</span>orget,
|
||||
<span style="color:#f59e0b;font-weight:600">U</span>pdate,
|
||||
<span style="color:#f59e0b;font-weight:600">N</span>ew —
|
||||
nothing is permanently destroyed. Forgotten cards are archived and can be remembered at any time.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CTA -->
|
||||
<section class="rl-section">
|
||||
<div class="rl-container" style="text-align:center">
|
||||
<h2 class="rl-heading" style="background:linear-gradient(135deg,#f59e0b,#f97316);-webkit-background-clip:text;background-clip:text">Start capturing</h2>
|
||||
<p class="rl-subtext">Notes, voice, clips, and code — all in one notebook.</p>
|
||||
<div class="rl-cta-row">
|
||||
<a href="https://demo.rspace.online/rnotes" class="rl-cta-primary"
|
||||
style="background:linear-gradient(135deg,#f59e0b,#d97706)">Open Notebook</a>
|
||||
<a href="/create-space" class="rl-cta-secondary">Create a Space</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="rl-back"><a href="/">← Back to rSpace</a></div>
|
||||
`;
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ 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";
|
||||
|
||||
const routes = new Hono();
|
||||
|
||||
|
|
@ -379,6 +380,7 @@ export const notesModule: RSpaceModule = {
|
|||
icon: "\u{1F4DD}",
|
||||
description: "Notebooks with rich-text notes, voice transcription, and collaboration",
|
||||
routes,
|
||||
landingPage: renderLanding,
|
||||
standaloneDomain: "rnotes.online",
|
||||
feeds: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,94 @@
|
|||
/**
|
||||
* Photos module landing page — rich content for rspace.online/rphotos
|
||||
*/
|
||||
|
||||
export function renderLanding(): string {
|
||||
return `
|
||||
<!-- Hero -->
|
||||
<div class="rl-hero">
|
||||
<span class="rl-tagline">rPhotos</span>
|
||||
<h1 class="rl-heading">Community Photo Commons</h1>
|
||||
<p class="rl-subtext">
|
||||
Share memories with your community, powered by Immich.
|
||||
Self-hosted, AI-organized, and privacy-first.
|
||||
</p>
|
||||
<div class="rl-cta-row">
|
||||
<a href="https://demo.rspace.online/rphotos" class="rl-cta-primary" id="ml-primary">Browse Gallery</a>
|
||||
<a href="/create-space" class="rl-cta-secondary">Create a Space</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- How it works -->
|
||||
<section class="rl-section">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center;">How It Works</h2>
|
||||
<div class="rl-grid-3">
|
||||
<div class="rl-step">
|
||||
<div class="rl-step__num">1</div>
|
||||
<h3>Upload Photos</h3>
|
||||
<p>Drop photos from any device. Bulk upload entire albums or individual shots.</p>
|
||||
</div>
|
||||
<div class="rl-step">
|
||||
<div class="rl-step__num">2</div>
|
||||
<h3>Auto-Organize</h3>
|
||||
<p>Immich's AI detects faces and objects, sorting your photos into smart groups automatically.</p>
|
||||
</div>
|
||||
<div class="rl-step">
|
||||
<div class="rl-step__num">3</div>
|
||||
<h3>Share Albums</h3>
|
||||
<p>Create shared albums for events, projects, or teams. Everyone in the space can contribute.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Features -->
|
||||
<section class="rl-section rl-section--alt">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center;">Features</h2>
|
||||
<p class="rl-subtext" style="text-align:center;">
|
||||
A full-featured photo platform that respects your community's data.
|
||||
</p>
|
||||
<div class="rl-grid-4">
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">🤖</div>
|
||||
<h3>Immich-Powered</h3>
|
||||
<p>AI-driven face and object detection for automatic organization, search, and discovery.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">📷</div>
|
||||
<h3>Shared Albums</h3>
|
||||
<p>Collaborative photo albums for events, gatherings, and ongoing projects. Everyone can add.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">📅</div>
|
||||
<h3>Timeline View</h3>
|
||||
<p>Browse photos chronologically. Scroll through your community's visual history day by day.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">🔒</div>
|
||||
<h3>Privacy-First</h3>
|
||||
<p>Self-hosted storage means your photos never leave your infrastructure. No third-party scanning.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CTA -->
|
||||
<section class="rl-section">
|
||||
<div class="rl-container" style="text-align:center;">
|
||||
<h2 class="rl-heading">Your photos, your community</h2>
|
||||
<p class="rl-subtext">
|
||||
Try the demo gallery or create a space to start your own photo commons.
|
||||
</p>
|
||||
<div class="rl-cta-row">
|
||||
<a href="https://demo.rspace.online/rphotos" class="rl-cta-primary">Browse Gallery</a>
|
||||
<a href="/create-space" class="rl-cta-secondary">Create a Space</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="rl-back">
|
||||
<a href="/">← Back to rSpace</a>
|
||||
</div>`;
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ import { Hono } from "hono";
|
|||
import { renderShell } from "../../server/shell";
|
||||
import { getModuleInfoList } from "../../shared/module";
|
||||
import type { RSpaceModule } from "../../shared/module";
|
||||
import { renderLanding } from "./landing";
|
||||
|
||||
const routes = new Hono();
|
||||
|
||||
|
|
@ -126,6 +127,7 @@ export const photosModule: RSpaceModule = {
|
|||
icon: "📸",
|
||||
description: "Community photo commons",
|
||||
routes,
|
||||
landingPage: renderLanding,
|
||||
standaloneDomain: "rphotos.online",
|
||||
feeds: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,221 @@
|
|||
/**
|
||||
* rPubs landing page — community pocket press.
|
||||
*/
|
||||
export function renderLanding(): string {
|
||||
return `
|
||||
<!-- Hero -->
|
||||
<div class="rl-hero">
|
||||
<span class="rl-tagline">rPubs</span>
|
||||
<h1 class="rl-heading">Write it. Press it. Share it.</h1>
|
||||
<p class="rl-subtext">
|
||||
Drop in a markdown document, pick a pocket format, and get a print-ready PDF in seconds.
|
||||
Group up with other authors to unlock bulk pricing through collaborative print runs.
|
||||
</p>
|
||||
<div class="rl-cta-row">
|
||||
<a href="https://demo.rspace.online/rpubs" class="rl-cta-primary" id="ml-primary">Try the Press</a>
|
||||
<a href="/rcart" class="rl-cta-secondary">Browse the Shop</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- How it works -->
|
||||
<section class="rl-section">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center">How it works</h2>
|
||||
<div class="rl-grid-3" style="margin-top:2rem">
|
||||
<div class="rl-step">
|
||||
<span class="rl-step__num">1</span>
|
||||
<h3>Write / paste</h3>
|
||||
<p>Drop in markdown or rich text. Headings, images, footnotes — it all just works.</p>
|
||||
</div>
|
||||
<div class="rl-step">
|
||||
<span class="rl-step__num">2</span>
|
||||
<h3>Press it</h3>
|
||||
<p>Pick a pocket format. rPubs typesets your document with Typst and generates a print-ready PDF.</p>
|
||||
</div>
|
||||
<div class="rl-step">
|
||||
<span class="rl-step__num">3</span>
|
||||
<h3>Print locally</h3>
|
||||
<p>Print at home, at a local shop, or list it on rCart for cosmolocal fulfillment.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Pocket Formats -->
|
||||
<section class="rl-section rl-section--alt">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center">Four pocket formats</h2>
|
||||
<p class="rl-subtext" style="text-align:center">From palm-sized zines to digest readers. All print-ready at 300 dpi with bleeds.</p>
|
||||
<div class="rl-grid-4" style="margin-top:2rem">
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19.5A2.5 2.5 0 016.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 014 19.5v-15A2.5 2.5 0 016.5 2z"/></svg>
|
||||
</div>
|
||||
<h3>A7 Pocket</h3>
|
||||
<p>74 × 105 mm — fits in a shirt pocket. Perfect for poems, manifestos, tiny zines.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19.5A2.5 2.5 0 016.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 014 19.5v-15A2.5 2.5 0 016.5 2z"/></svg>
|
||||
</div>
|
||||
<h3>Quarter Letter</h3>
|
||||
<p>4.25 × 5.5" — half a half-sheet. Great for chapbooks and field guides.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19.5A2.5 2.5 0 016.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 014 19.5v-15A2.5 2.5 0 016.5 2z"/></svg>
|
||||
</div>
|
||||
<h3>A6 Booklet</h3>
|
||||
<p>105 × 148 mm — postcard size. The workhorse format for essays and readers.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19.5A2.5 2.5 0 016.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 014 19.5v-15A2.5 2.5 0 016.5 2z"/></svg>
|
||||
</div>
|
||||
<h3>Digest</h3>
|
||||
<p>5.5 × 8.5" — half-letter. Standard trade paperback for longer works.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Group Buys -->
|
||||
<section class="rl-section">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center">Group buys — better together</h2>
|
||||
<p class="rl-subtext" style="text-align:center">Pool orders across titles. The more copies in a run, the cheaper each one gets.</p>
|
||||
<div class="rl-grid-3" style="margin-top:2rem">
|
||||
<div class="rl-card rl-card--center">
|
||||
<h3>25+ copies</h3>
|
||||
<p>Saddle-stitch binding</p>
|
||||
<p style="font-size:1.5rem;font-weight:700;color:#14b8a6;margin:0.5rem 0">$8</p>
|
||||
<p style="font-size:0.75rem;color:#64748b">per copy</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<h3>50+ copies</h3>
|
||||
<p>Perfect-bind</p>
|
||||
<p style="font-size:1.5rem;font-weight:700;color:#14b8a6;margin:0.5rem 0">$6</p>
|
||||
<span class="rl-badge">Save 25%</span>
|
||||
<p style="font-size:0.75rem;color:#64748b;margin-top:0.5rem">per copy</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<h3>100+ copies</h3>
|
||||
<p>Trade edition</p>
|
||||
<p style="font-size:1.5rem;font-weight:700;color:#14b8a6;margin:0.5rem 0">$4.50</p>
|
||||
<span class="rl-badge">Save 44%</span>
|
||||
<p style="font-size:0.75rem;color:#64748b;margin-top:0.5rem">per copy</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- How collaborative print runs work -->
|
||||
<section class="rl-section rl-section--alt">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center">How collaborative print runs work</h2>
|
||||
<div class="rl-grid-4" style="margin-top:2rem">
|
||||
<div class="rl-step">
|
||||
<span class="rl-step__num">1</span>
|
||||
<h3>Authors list titles</h3>
|
||||
<p>Publish your print-ready artifact to the rCart catalog.</p>
|
||||
</div>
|
||||
<div class="rl-step">
|
||||
<span class="rl-step__num">2</span>
|
||||
<h3>Readers pre-order</h3>
|
||||
<p>Buyers add copies to a shared cart. Orders accumulate toward tier thresholds.</p>
|
||||
</div>
|
||||
<div class="rl-step">
|
||||
<span class="rl-step__num">3</span>
|
||||
<h3>Threshold met</h3>
|
||||
<p>When the combined order hits a tier, the print run triggers automatically.</p>
|
||||
</div>
|
||||
<div class="rl-step">
|
||||
<span class="rl-step__num">4</span>
|
||||
<h3>Local fulfillment</h3>
|
||||
<p>The nearest cosmolocal provider prints and ships. Revenue splits to creator, community, and provider.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Cross-title batching -->
|
||||
<section class="rl-section">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center">Cross-title batching</h2>
|
||||
<p class="rl-subtext" style="text-align:center">
|
||||
Orders from <em>different</em> titles count toward the same tier. A community reading list
|
||||
can hit trade-edition pricing even if no single title has 100 orders.
|
||||
</p>
|
||||
<div class="rl-card" style="max-width:640px;margin:2rem auto 0">
|
||||
<h3 style="margin-bottom:1rem">Example batch</h3>
|
||||
<div style="display:flex;flex-direction:column;gap:0.5rem">
|
||||
<div style="display:flex;justify-content:space-between;font-size:0.875rem">
|
||||
<span style="color:#e2e8f0">The Commons</span><span style="color:#14b8a6;font-weight:600">35 copies</span>
|
||||
</div>
|
||||
<div style="display:flex;justify-content:space-between;font-size:0.875rem">
|
||||
<span style="color:#e2e8f0">Mycelial Networks</span><span style="color:#14b8a6;font-weight:600">40 copies</span>
|
||||
</div>
|
||||
<div style="display:flex;justify-content:space-between;font-size:0.875rem">
|
||||
<span style="color:#e2e8f0">Cosmolocal Reader</span><span style="color:#14b8a6;font-weight:600">30 copies</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rl-divider"><span>combined</span></div>
|
||||
<div style="text-align:center">
|
||||
<span style="font-size:1.5rem;font-weight:700;color:#14b8a6">105 copies</span>
|
||||
<span style="display:block;font-size:0.8rem;color:#64748b;margin-top:0.25rem">= Trade edition @ $4.50/copy</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- rCart integration -->
|
||||
<section class="rl-section rl-section--alt">
|
||||
<div class="rl-container">
|
||||
<div class="rl-grid-2">
|
||||
<div>
|
||||
<h2 class="rl-heading">rCart integration</h2>
|
||||
<p class="rl-subtext" style="margin-bottom:1.5rem">
|
||||
Group purchasing is built right into the shop. Every rPubs artifact can be listed,
|
||||
batched, and fulfilled through rCart.
|
||||
</p>
|
||||
<ul class="rl-check-list">
|
||||
<li><strong>One-click listing</strong> — publish to the rCart catalog straight from the press</li>
|
||||
<li><strong>Group carts</strong> — space members pool orders automatically</li>
|
||||
<li><strong>Revenue splits</strong> — creator, community, and provider shares via <a href="/rfunds" style="color:#14b8a6">rFunds</a></li>
|
||||
<li><strong>Cosmolocal fulfillment</strong> — nearest provider prints and ships</li>
|
||||
<li><strong>Order tracking</strong> — real-time status from press to doorstep</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center" style="display:flex;align-items:center;justify-content:center">
|
||||
<div>
|
||||
<div class="rl-icon-box" style="margin:0 auto 1rem">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="9" cy="21" r="1"/><circle cx="20" cy="21" r="1"/><path d="M1 1h4l2.68 13.39a2 2 0 002 1.61h9.72a2 2 0 002-1.61L23 6H6"/></svg>
|
||||
</div>
|
||||
<h3>Shop + Press</h3>
|
||||
<p>rPubs creates the artifact.<br>rCart sells and fulfills it.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Cosmolocal -->
|
||||
<section class="rl-section">
|
||||
<div class="rl-container" style="text-align:center">
|
||||
<span class="rl-tagline">rPubs × Cosmolocal</span>
|
||||
<h2 class="rl-heading">Design global, manufacture local</h2>
|
||||
<p class="rl-subtext">
|
||||
Every print run is routed to the nearest capable provider. Reduce shipping emissions,
|
||||
support local economies, and still benefit from shared design and pooled demand.
|
||||
</p>
|
||||
<div class="rl-cta-row">
|
||||
<a href="https://demo.rspace.online/rpubs" class="rl-cta-primary" id="ml-primary">Start pressing</a>
|
||||
<a href="/rcart" class="rl-cta-secondary">Visit the Shop</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="rl-back">
|
||||
<a href="/">← Back to rSpace</a>
|
||||
</div>`;
|
||||
}
|
||||
|
|
@ -16,6 +16,7 @@ import type { BookFormat } from "./formats";
|
|||
import { renderShell } from "../../server/shell";
|
||||
import { getModuleInfoList } from "../../shared/module";
|
||||
import type { RSpaceModule } from "../../shared/module";
|
||||
import { renderLanding } from "./landing";
|
||||
|
||||
const ARTIFACTS_DIR = process.env.ARTIFACTS_DIR || "/tmp/rpubs-artifacts";
|
||||
|
||||
|
|
@ -342,6 +343,7 @@ export const pubsModule: RSpaceModule = {
|
|||
description: "Drop in a document, get a pocket book",
|
||||
routes,
|
||||
standaloneDomain: "rpubs.online",
|
||||
landingPage: renderLanding,
|
||||
feeds: [
|
||||
{
|
||||
id: "publications",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,165 @@
|
|||
/**
|
||||
* rSocials — rich landing page body.
|
||||
* Returned by landingPage() in the module export;
|
||||
* the shell wraps it with header, CSS, and analytics.
|
||||
*/
|
||||
export function renderLanding(): string {
|
||||
const demo = "https://demo.rspace.online/rsocials";
|
||||
|
||||
return `
|
||||
<!-- Hero -->
|
||||
<section class="rl-hero">
|
||||
<span class="rl-tagline">rSocials</span>
|
||||
<h1 class="rl-heading">Social Media Under Your Control</h1>
|
||||
<p class="rl-subtext">
|
||||
Schedule, publish, and analyze across every major platform — all
|
||||
from your self-hosted rSpace instance. No per-seat pricing, no
|
||||
third-party data mining.
|
||||
</p>
|
||||
<div class="rl-cta-row">
|
||||
<a href="${demo}" class="rl-cta-primary" id="ml-primary">Try Demo</a>
|
||||
<a href="/create-space" class="rl-cta-secondary">Create a Space</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- How It Works -->
|
||||
<section class="rl-section">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center">How It Works</h2>
|
||||
<div class="rl-grid-3">
|
||||
<div class="rl-step">
|
||||
<div class="rl-step__num">1</div>
|
||||
<h3>Connect Platforms</h3>
|
||||
<p>Link your X/Twitter, LinkedIn, Instagram, TikTok, and more in one place.</p>
|
||||
</div>
|
||||
<div class="rl-step">
|
||||
<div class="rl-step__num">2</div>
|
||||
<h3>Schedule Content</h3>
|
||||
<p>Draft posts, attach media, pick dates, and queue them up across every channel.</p>
|
||||
</div>
|
||||
<div class="rl-step">
|
||||
<div class="rl-step__num">3</div>
|
||||
<h3>Publish & Analyze</h3>
|
||||
<p>Posts go live on schedule. Track engagement, reach, and growth from a single dashboard.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Supported Platforms -->
|
||||
<section class="rl-section rl-section--alt">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center">Supported Platforms</h2>
|
||||
<p class="rl-subtext" style="text-align:center">
|
||||
Publish everywhere your audience lives — 16 platforms and counting.
|
||||
</p>
|
||||
<div class="rl-grid-4">
|
||||
<div class="rl-card rl-card--center"><h3>X / Twitter</h3></div>
|
||||
<div class="rl-card rl-card--center"><h3>LinkedIn</h3></div>
|
||||
<div class="rl-card rl-card--center"><h3>Facebook</h3></div>
|
||||
<div class="rl-card rl-card--center"><h3>Instagram</h3></div>
|
||||
<div class="rl-card rl-card--center"><h3>TikTok</h3></div>
|
||||
<div class="rl-card rl-card--center"><h3>YouTube</h3></div>
|
||||
<div class="rl-card rl-card--center"><h3>Pinterest</h3></div>
|
||||
<div class="rl-card rl-card--center"><h3>Reddit</h3></div>
|
||||
<div class="rl-card rl-card--center"><h3>Threads</h3></div>
|
||||
<div class="rl-card rl-card--center"><h3>BlueSky</h3></div>
|
||||
<div class="rl-card rl-card--center"><h3>Mastodon</h3></div>
|
||||
<div class="rl-card rl-card--center"><h3>Dribbble</h3></div>
|
||||
<div class="rl-card rl-card--center"><h3>Slack</h3></div>
|
||||
<div class="rl-card rl-card--center"><h3>Discord</h3></div>
|
||||
<div class="rl-card rl-card--center"><h3>Telegram</h3></div>
|
||||
<div class="rl-card rl-card--center"><h3>RSS</h3></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Features -->
|
||||
<section class="rl-section">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center">Features</h2>
|
||||
<div class="rl-grid-4">
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">📅</div>
|
||||
<h3>Calendar View</h3>
|
||||
<p>Visual month/week/day planner so you never double-post or miss a slot.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">✍</div>
|
||||
<h3>AI Copywriting</h3>
|
||||
<p>Generate caption variations, hashtags, and thread hooks with a single click.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">🎨</div>
|
||||
<h3>Post Designer</h3>
|
||||
<p>Resize images, add text overlays, and preview how posts look on each platform.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">👥</div>
|
||||
<h3>Team Collaboration</h3>
|
||||
<p>Invite editors & approvers. Review queues keep quality high.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">🛠</div>
|
||||
<h3>Per-Platform Optimization</h3>
|
||||
<p>Tailor copy, image crops, and hashtags for each network automatically.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">📶</div>
|
||||
<h3>RSS Auto-Post</h3>
|
||||
<p>Watch any RSS feed and auto-publish new items to selected channels.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">📈</div>
|
||||
<h3>Analytics Dashboard</h3>
|
||||
<p>Unified metrics across all platforms: impressions, clicks, engagement rate.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">🌐</div>
|
||||
<h3>rSpace Ecosystem</h3>
|
||||
<p>Connect to <a href="/rpubs" style="color:#14b8a6">rPubs</a>, <a href="/rforum" style="color:#14b8a6">rForum</a>, and more for seamless cross-posting.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Why Self-Hosted -->
|
||||
<section class="rl-section rl-section--alt">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center">Why Self-Hosted?</h2>
|
||||
<div class="rl-grid-3">
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">🔒</div>
|
||||
<h3>Data Sovereignty</h3>
|
||||
<p>API tokens and analytics stay on your server. No third-party vendor sees your audience data.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">💰</div>
|
||||
<h3>No Per-Seat Pricing</h3>
|
||||
<p>Add unlimited team members. Pay only for hosting, not per-user SaaS fees.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">🔌</div>
|
||||
<h3>Open Source & Extensible</h3>
|
||||
<p>Fork it, theme it, add custom integrations. Postiz under the hood, rSpace on top.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CTA -->
|
||||
<section class="rl-section">
|
||||
<div class="rl-container" style="text-align:center">
|
||||
<h2 class="rl-heading">Ready to Own Your Social Presence?</h2>
|
||||
<p class="rl-subtext">
|
||||
Deploy rSocials in your rSpace and start scheduling in minutes.
|
||||
</p>
|
||||
<div class="rl-cta-row">
|
||||
<a href="${demo}" class="rl-cta-primary">Try Demo</a>
|
||||
<a href="/create-space" class="rl-cta-secondary">Create a Space</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="rl-back"><a href="/">← Back to rSpace</a></div>`;
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@ import { Hono } from "hono";
|
|||
import { renderShell } from "../../server/shell";
|
||||
import { getModuleInfoList } from "../../shared/module";
|
||||
import type { RSpaceModule } from "../../shared/module";
|
||||
import { renderLanding } from "./landing";
|
||||
|
||||
const routes = new Hono();
|
||||
|
||||
|
|
@ -187,6 +188,7 @@ export const socialsModule: RSpaceModule = {
|
|||
description: "Federated social feed aggregator for communities",
|
||||
routes,
|
||||
standaloneDomain: "rsocials.online",
|
||||
landingPage: renderLanding,
|
||||
feeds: [
|
||||
{
|
||||
id: "social-feed",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,94 @@
|
|||
/**
|
||||
* Splat module landing page — rich content for rspace.online/rsplat
|
||||
*/
|
||||
|
||||
export function renderLanding(): string {
|
||||
return `
|
||||
<!-- Hero -->
|
||||
<div class="rl-hero">
|
||||
<span class="rl-tagline">rSplat</span>
|
||||
<h1 class="rl-heading">3D Gaussian Splats on the Infinite Canvas</h1>
|
||||
<p class="rl-subtext">
|
||||
View, share, and gate 3D scenes captured with Gaussian splatting.
|
||||
Three.js-powered viewing with optional x402 micropayment gating.
|
||||
</p>
|
||||
<div class="rl-cta-row">
|
||||
<a href="https://demo.rspace.online/rsplat" class="rl-cta-primary" id="ml-primary">Explore Splats</a>
|
||||
<a href="/create-space" class="rl-cta-secondary">Create a Space</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- How it works -->
|
||||
<section class="rl-section">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center;">How It Works</h2>
|
||||
<div class="rl-grid-3">
|
||||
<div class="rl-step">
|
||||
<div class="rl-step__num">1</div>
|
||||
<h3>Upload .ply/.splat Files</h3>
|
||||
<p>Upload Gaussian splat files captured from real-world scenes or generated from photos and video.</p>
|
||||
</div>
|
||||
<div class="rl-step">
|
||||
<div class="rl-step__num">2</div>
|
||||
<h3>View in Browser</h3>
|
||||
<p>Explore 3D scenes directly in the browser with orbit controls, zoom, and pan. No app install needed.</p>
|
||||
</div>
|
||||
<div class="rl-step">
|
||||
<div class="rl-step__num">3</div>
|
||||
<h3>Gate with x402 Micropayments</h3>
|
||||
<p>Optionally gate uploads with HTTP 402 micropayments. Monetize premium 3D content natively.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Features -->
|
||||
<section class="rl-section rl-section--alt">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center;">Features</h2>
|
||||
<p class="rl-subtext" style="text-align:center;">
|
||||
A complete pipeline from capture to community gallery.
|
||||
</p>
|
||||
<div class="rl-grid-4">
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">🎨</div>
|
||||
<h3>Three.js Viewer</h3>
|
||||
<p>Hardware-accelerated WebGL rendering with orbit controls, smooth camera, and full-screen mode.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">🖼</div>
|
||||
<h3>Canvas Integration</h3>
|
||||
<p>Embed splats as spatial elements on the rSpace infinite canvas alongside other module content.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">💰</div>
|
||||
<h3>x402 Gating</h3>
|
||||
<p>Monetize 3D content with HTTP 402 micropayments. Viewers pay per-view or per-download automatically.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">🌄</div>
|
||||
<h3>Community Gallery</h3>
|
||||
<p>Browse, search, and discover 3D scenes shared by your community. Sorted by recent, popular, or tagged.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CTA -->
|
||||
<section class="rl-section">
|
||||
<div class="rl-container" style="text-align:center;">
|
||||
<h2 class="rl-heading">Step into 3D</h2>
|
||||
<p class="rl-subtext">
|
||||
Explore the demo gallery or upload your own Gaussian splats.
|
||||
</p>
|
||||
<div class="rl-cta-row">
|
||||
<a href="https://demo.rspace.online/rsplat" class="rl-cta-primary">Explore Splats</a>
|
||||
<a href="/create-space" class="rl-cta-secondary">Create a Space</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="rl-back">
|
||||
<a href="/">← Back to rSpace</a>
|
||||
</div>`;
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ import { sql } from "../../shared/db/pool";
|
|||
import { renderShell } from "../../server/shell";
|
||||
import { getModuleInfoList } from "../../shared/module";
|
||||
import type { RSpaceModule } from "../../shared/module";
|
||||
import { renderLanding } from "./landing";
|
||||
import {
|
||||
verifyEncryptIDToken,
|
||||
extractToken,
|
||||
|
|
@ -539,6 +540,7 @@ export const splatModule: RSpaceModule = {
|
|||
icon: "🔮",
|
||||
description: "3D Gaussian splat viewer",
|
||||
routes,
|
||||
landingPage: renderLanding,
|
||||
standaloneDomain: "rsplat.online",
|
||||
hidden: true,
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,123 @@
|
|||
/**
|
||||
* Swag module landing page — rich content for rspace.online/rswag
|
||||
*/
|
||||
|
||||
export function renderLanding(): string {
|
||||
return `
|
||||
<!-- Hero -->
|
||||
<div class="rl-hero">
|
||||
<span class="rl-tagline">rSwag</span>
|
||||
<h1 class="rl-heading">Design Community Merch</h1>
|
||||
<p class="rl-subtext">
|
||||
Stickers, posters, and tees from your browser.
|
||||
Upload a design, pick a product, and get print-ready files instantly.
|
||||
</p>
|
||||
<div class="rl-cta-row">
|
||||
<a href="https://demo.rspace.online/rswag" class="rl-cta-primary" id="ml-primary">Start Designing</a>
|
||||
<a href="/create-space" class="rl-cta-secondary">Create a Space</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- How it works -->
|
||||
<section class="rl-section">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center;">How It Works</h2>
|
||||
<div class="rl-grid-3">
|
||||
<div class="rl-step">
|
||||
<div class="rl-step__num">1</div>
|
||||
<h3>Choose a Product</h3>
|
||||
<p>Pick from stickers, posters, t-shirts, or patches. Each product has print-ready specs built in.</p>
|
||||
</div>
|
||||
<div class="rl-step">
|
||||
<div class="rl-step__num">2</div>
|
||||
<h3>Upload Your Design</h3>
|
||||
<p>Drop in your artwork. Sharp resizes, crops, and optimizes for the selected product automatically.</p>
|
||||
</div>
|
||||
<div class="rl-step">
|
||||
<div class="rl-step__num">3</div>
|
||||
<h3>Preview & Order</h3>
|
||||
<p>See a live mockup, download print-ready files, or send directly to rCart for community ordering.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Products -->
|
||||
<section class="rl-section rl-section--alt">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center;">Products</h2>
|
||||
<p class="rl-subtext" style="text-align:center;">
|
||||
Four product types, each with professional print specs.
|
||||
</p>
|
||||
<div class="rl-grid-4">
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">🧾</div>
|
||||
<h3>Stickers</h3>
|
||||
<p>Vinyl die-cut stickers. Weatherproof, UV-resistant, and ready for laptops, bottles, and walls.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">🖼</div>
|
||||
<h3>Posters</h3>
|
||||
<p>A3 and A2 prints at 300 DPI. Gallery-quality paper with bleed margins included.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">👕</div>
|
||||
<h3>T-Shirts</h3>
|
||||
<p>Direct-to-garment (DTG) printing. Full-color designs on comfortable, pre-shrunk cotton.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">🧵</div>
|
||||
<h3>Patches</h3>
|
||||
<p>Embroidered patches with iron-on backing. Perfect for jackets, bags, and hats.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Features -->
|
||||
<section class="rl-section">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center;">Features</h2>
|
||||
<div class="rl-grid-4">
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">👁</div>
|
||||
<h3>Live Preview</h3>
|
||||
<p>See your design on product mockups before committing. Adjust and iterate in real time.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">🖨</div>
|
||||
<h3>Print-Ready Export</h3>
|
||||
<p>Download files with correct DPI, bleed margins, and color profiles for professional printing.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">⚡</div>
|
||||
<h3>Sharp Processing</h3>
|
||||
<p>Server-side image processing with Sharp. Fast resizing, format conversion, and optimization.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">🛒</div>
|
||||
<h3>rCart Integration</h3>
|
||||
<p>Send finished designs directly to <a href="/rcart" style="color:#14b8a6;">rCart</a> for community ordering and fulfillment.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CTA -->
|
||||
<section class="rl-section rl-section--alt">
|
||||
<div class="rl-container" style="text-align:center;">
|
||||
<h2 class="rl-heading">Make something your community can wear</h2>
|
||||
<p class="rl-subtext">
|
||||
Try the designer or create a space to start your merch line.
|
||||
</p>
|
||||
<div class="rl-cta-row">
|
||||
<a href="https://demo.rspace.online/rswag" class="rl-cta-primary">Start Designing</a>
|
||||
<a href="/create-space" class="rl-cta-secondary">Create a Space</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="rl-back">
|
||||
<a href="/">← Back to rSpace</a>
|
||||
</div>`;
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@ import { processImage } from "./process-image";
|
|||
import { renderShell } from "../../server/shell";
|
||||
import { getModuleInfoList } from "../../shared/module";
|
||||
import type { RSpaceModule } from "../../shared/module";
|
||||
import { renderLanding } from "./landing";
|
||||
|
||||
const routes = new Hono();
|
||||
|
||||
|
|
@ -246,6 +247,7 @@ export const swagModule: RSpaceModule = {
|
|||
icon: "\u{1F3A8}",
|
||||
description: "Design print-ready swag: stickers, posters, tees",
|
||||
routes,
|
||||
landingPage: renderLanding,
|
||||
standaloneDomain: "rswag.online",
|
||||
feeds: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,212 @@
|
|||
/**
|
||||
* Trips module landing page — static HTML, no React.
|
||||
*/
|
||||
export function renderLanding(): string {
|
||||
return `
|
||||
<!-- Hero -->
|
||||
<div class="rl-hero">
|
||||
<span class="rl-tagline">rTrips</span>
|
||||
<h1 class="rl-heading">Plan Your Trip, Naturally</h1>
|
||||
<p class="rl-subtext">
|
||||
Describe your dream trip in plain language. We'll structure it into
|
||||
itineraries, budgets, and bookings — then give you a collaborative
|
||||
canvas to plan together in real-time.
|
||||
</p>
|
||||
<div class="rl-cta-row">
|
||||
<a href="https://demo.rspace.online/rtrips" class="rl-cta-primary" id="ml-primary">Try Demo</a>
|
||||
<a href="/create-space" class="rl-cta-secondary">Create a Space</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- How it Works -->
|
||||
<section class="rl-section">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center">How It Works</h2>
|
||||
<p class="rl-subtext" style="text-align:center">Three steps from dream to departure.</p>
|
||||
<div class="rl-grid-3">
|
||||
<div class="rl-step">
|
||||
<div class="rl-step__num">1</div>
|
||||
<h3>Describe It</h3>
|
||||
<p>
|
||||
Tell us about your trip in natural language. “Fly from Toronto to Bali
|
||||
for 2 weeks in March, budget $3000.” We parse it into structured data
|
||||
you can refine.
|
||||
</p>
|
||||
</div>
|
||||
<div class="rl-step">
|
||||
<div class="rl-step__num">2</div>
|
||||
<h3>We Structure It</h3>
|
||||
<p>
|
||||
AI extracts destinations, dates, budgets, and bookings into organized views.
|
||||
Edit itineraries, track expenses, manage packing lists — all structured
|
||||
and searchable.
|
||||
</p>
|
||||
</div>
|
||||
<div class="rl-step">
|
||||
<div class="rl-step__num">3</div>
|
||||
<h3>Collaborate on Canvas</h3>
|
||||
<p>
|
||||
Open the collaborative canvas to plan visually with your travel partners.
|
||||
Drag destinations, connect itineraries, and brainstorm together in real-time
|
||||
or async.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Features -->
|
||||
<section class="rl-section rl-section--alt">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center">Everything You Need to Travel Together</h2>
|
||||
<p class="rl-subtext" style="text-align:center">
|
||||
rTrips brings every piece of trip planning into one place — so your group
|
||||
spends less time coordinating and more time exploring.
|
||||
</p>
|
||||
<div class="rl-grid-3">
|
||||
<div class="rl-card">
|
||||
<div class="rl-icon-box">
|
||||
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M18 18.72a9.094 9.094 0 003.741-.479 3 3 0 00-4.682-2.72m.94 3.198l.001.031c0 .225-.012.447-.037.666A11.944 11.944 0 0112 21c-2.17 0-4.207-.576-5.963-1.584A6.062 6.062 0 016 18.719m12 0a5.971 5.971 0 00-.941-3.197m0 0A5.995 5.995 0 0012 12.75a5.995 5.995 0 00-5.058 2.772m0 0a3 3 0 00-4.681 2.72 8.986 8.986 0 003.74.477m.94-3.197a5.971 5.971 0 00-.94 3.197M15 6.75a3 3 0 11-6 0 3 3 0 016 0zm6 3a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0zm-13.5 0a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3>Collaborative Itineraries</h3>
|
||||
<p>
|
||||
Build day-by-day plans together in real-time. Everyone can add destinations,
|
||||
suggest activities, and rearrange the schedule — changes sync instantly.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="rl-card">
|
||||
<div class="rl-icon-box">
|
||||
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09zM18.259 8.715L18 9.75l-.259-1.035a3.375 3.375 0 00-2.455-2.456L14.25 6l1.036-.259a3.375 3.375 0 002.455-2.456L18 2.25l.259 1.035a3.375 3.375 0 002.455 2.456L21.75 6l-1.036.259a3.375 3.375 0 00-2.455 2.456z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3>Smart Suggestions</h3>
|
||||
<p>
|
||||
AI-powered recommendations for destinations, restaurants, and activities based
|
||||
on your group's interests, budget, and travel dates.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="rl-card">
|
||||
<div class="rl-icon-box">
|
||||
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 18.75a60.07 60.07 0 0115.797 2.101c.727.198 1.453-.342 1.453-1.096V18.75M3.75 4.5v.75A.75.75 0 013 6h-.75m0 0v-.375c0-.621.504-1.125 1.125-1.125H20.25M2.25 6v9m18-10.5v.75c0 .414.336.75.75.75h.75m-1.5-1.5h.375c.621 0 1.125.504 1.125 1.125v9.75c0 .621-.504 1.125-1.125 1.125h-.375m1.5-1.5H21a.75.75 0 00-.75.75v.75m0 0H3.75m0 0h-.375a1.125 1.125 0 01-1.125-1.125V15m1.5 1.5v-.75A.75.75 0 003 15h-.75M15 10.5a3 3 0 11-6 0 3 3 0 016 0zm3 0h.008v.008H18V10.5zm-12 0h.008v.008H6V10.5z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3>Budget Tracking</h3>
|
||||
<p>
|
||||
Split costs, track expenses across the group, and keep a running total so
|
||||
everyone knows exactly where the money goes. No more messy spreadsheets.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="rl-card">
|
||||
<div class="rl-icon-box">
|
||||
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 6.75V15m6-6v8.25m.503 3.498l4.875-2.437c.381-.19.622-.58.622-1.006V4.82c0-.836-.88-1.38-1.628-1.006l-3.869 1.934c-.317.159-.69.159-1.006 0L9.503 3.252a1.125 1.125 0 00-1.006 0L3.622 5.689C3.24 5.88 3 6.27 3 6.695V19.18c0 .836.88 1.38 1.628 1.006l3.869-1.934c.317-.159.69-.159 1.006 0l4.994 2.497c.317.158.69.158 1.006 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3>Map Integration</h3>
|
||||
<p>
|
||||
Visualize your entire trip on <a href="/rmaps" style="color:#14b8a6">rMaps</a>. See routes between destinations,
|
||||
nearby points of interest, and real-time location sharing during travel days.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="rl-card">
|
||||
<div class="rl-icon-box">
|
||||
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12h3.75M9 15h3.75M9 18h3.75m3 .75H18a2.25 2.25 0 002.25-2.25V6.108c0-1.135-.845-2.098-1.976-2.192a48.424 48.424 0 00-1.123-.08m-5.801 0c-.065.21-.1.433-.1.664 0 .414.336.75.75.75h4.5a.75.75 0 00.75-.75 2.25 2.25 0 00-.1-.664m-5.8 0A2.251 2.251 0 0113.5 2.25H15a2.25 2.25 0 012.15 1.586m-5.8 0c-.376.023-.75.05-1.124.08C9.095 4.01 8.25 4.973 8.25 6.108V8.25m0 0H4.875c-.621 0-1.125.504-1.125 1.125v11.25c0 .621.504 1.125 1.125 1.125h9.75c.621 0 1.125-.504 1.125-1.125V9.375c0-.621-.504-1.125-1.125-1.125H8.25zM6.75 12h.008v.008H6.75V12zm0 3h.008v.008H6.75V15zm0 3h.008v.008H6.75V18z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3>Packing Lists</h3>
|
||||
<p>
|
||||
Shared checklists so nothing gets forgotten. Assign items to people, mark
|
||||
off as you pack, and see at a glance what the group still needs.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="rl-card">
|
||||
<div class="rl-icon-box">
|
||||
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5M16.5 12L12 16.5m0 0L7.5 12m4.5 4.5V3" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3>Offline Access</h3>
|
||||
<p>
|
||||
Download your full itinerary, maps, and booking confirmations for travel
|
||||
without connectivity. Everything you need, even without a signal.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Built for Groups -->
|
||||
<section class="rl-section">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center">Built for Groups</h2>
|
||||
<p class="rl-subtext" style="text-align:center">
|
||||
Solo trip planners are everywhere. rTrips is purpose-built for the messy, beautiful
|
||||
reality of traveling with other people.
|
||||
</p>
|
||||
<div class="rl-grid-3">
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">
|
||||
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M21 8.25c0-2.485-2.099-4.5-4.688-4.5-1.935 0-3.597 1.126-4.312 2.733-.715-1.607-2.377-2.733-4.313-2.733C5.1 3.75 3 5.765 3 8.25c0 7.22 9 12 9 12s9-4.78 9-12z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3>Friends & Family</h3>
|
||||
<p>
|
||||
Family reunions, friend getaways, multi-generational trips. Everyone contributes
|
||||
ideas, votes on restaurants, and stays in sync without endless group chats.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">
|
||||
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M20.25 14.15v4.25c0 1.094-.787 2.036-1.872 2.18-2.087.277-4.216.42-6.378.42s-4.291-.143-6.378-.42c-1.085-.144-1.872-1.086-1.872-2.18v-4.25m16.5 0a2.18 2.18 0 00.75-1.661V8.706c0-1.081-.768-2.015-1.837-2.175a48.114 48.114 0 00-3.413-.387m4.5 8.006c-.194.165-.42.295-.673.38A23.978 23.978 0 0112 15.75c-2.648 0-5.195-.429-7.577-1.22a2.016 2.016 0 01-.673-.38m0 0A2.18 2.18 0 013 12.489V8.706c0-1.081.768-2.015 1.837-2.175a48.111 48.111 0 013.413-.387m7.5 0V5.25A2.25 2.25 0 0013.5 3h-3a2.25 2.25 0 00-2.25 2.25v.894m7.5 0a48.667 48.667 0 00-7.5 0M12 12.75h.008v.008H12v-.008z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3>Teams & Offsites</h3>
|
||||
<p>
|
||||
Company retreats, conference travel, team offsites. Coordinate logistics,
|
||||
share flight details, and manage group bookings from a single shared workspace.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">
|
||||
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12.75 3.03v.568c0 .334.148.65.405.864l1.068.89c.442.369.535 1.01.216 1.49l-.51.766a2.25 2.25 0 01-1.161.886l-.143.048a1.107 1.107 0 00-.57 1.664c.369.555.169 1.307-.427 1.605L9 13.125l.423 1.059a.956.956 0 01-1.652.928l-.679-.906a1.125 1.125 0 00-1.906.172L4.5 15.75l-.612.153M12.75 3.031a9 9 0 00-8.862 12.872M12.75 3.031a9 9 0 016.69 14.036m0 0l-.177-.529A2.25 2.25 0 0017.128 15H16.5l-.324-.324a1.453 1.453 0 00-2.328.377l-.036.073a1.586 1.586 0 01-.982.816l-.99.282c-.55.157-.894.702-.8 1.267l.073.438a2.25 2.25 0 01-1.228 2.446L10.5 21l-.652-.174M12.75 3.031l.176.53" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3>Retreats & Events</h3>
|
||||
<p>
|
||||
Yoga retreats, wedding trips, festival crews. Organize large groups with
|
||||
sub-itineraries, optional activities, and shared costs that stay transparent.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CTA -->
|
||||
<section class="rl-section rl-section--alt">
|
||||
<div class="rl-container" style="text-align:center">
|
||||
<h2 class="rl-heading">Ready to plan your next adventure?</h2>
|
||||
<p class="rl-subtext">Just describe where you want to go. We'll handle the rest.</p>
|
||||
<div class="rl-cta-row">
|
||||
<a href="https://demo.rspace.online/rtrips" class="rl-cta-primary">Plan a Trip</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="rl-back"><a href="/">← Back to rSpace</a></div>
|
||||
`;
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ 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";
|
||||
|
||||
const OSRM_URL = process.env.OSRM_URL || "http://osrm-backend:5000";
|
||||
|
||||
|
|
@ -271,6 +272,7 @@ export const tripsModule: RSpaceModule = {
|
|||
icon: "\u{2708}\u{FE0F}",
|
||||
description: "Collaborative trip planner with itinerary, bookings, and expense splitting",
|
||||
routes,
|
||||
landingPage: renderLanding,
|
||||
standaloneDomain: "rtrips.online",
|
||||
feeds: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,110 @@
|
|||
/**
|
||||
* Tube module landing page — rich content for rspace.online/rtube
|
||||
*/
|
||||
|
||||
export function renderLanding(): string {
|
||||
return `
|
||||
<!-- Hero -->
|
||||
<div class="rl-hero">
|
||||
<span class="rl-tagline">rTube</span>
|
||||
<h1 class="rl-heading">Community Video Platform</h1>
|
||||
<p class="rl-subtext">
|
||||
Host, stream, and share video without big tech.
|
||||
Your community's video library with HLS streaming and RTMP ingest.
|
||||
</p>
|
||||
<div class="rl-cta-row">
|
||||
<a href="https://demo.rspace.online/rtube" class="rl-cta-primary" id="ml-primary">Start Streaming</a>
|
||||
<a href="/create-space" class="rl-cta-secondary">Create a Space</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- How it works -->
|
||||
<section class="rl-section">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center;">How It Works</h2>
|
||||
<div class="rl-grid-3">
|
||||
<div class="rl-step">
|
||||
<div class="rl-step__num">1</div>
|
||||
<h3>Upload Videos</h3>
|
||||
<p>Drop video files in any major format. MP4, WebM, MKV, MOV -- they all work.</p>
|
||||
</div>
|
||||
<div class="rl-step">
|
||||
<div class="rl-step__num">2</div>
|
||||
<h3>Stream Live</h3>
|
||||
<p>Point OBS or any RTMP-compatible software at your space's ingest URL and go live instantly.</p>
|
||||
</div>
|
||||
<div class="rl-step">
|
||||
<div class="rl-step__num">3</div>
|
||||
<h3>Share with Your Community</h3>
|
||||
<p>Videos are organized by space. Share links, embed on your canvas, or browse the community library.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Features -->
|
||||
<section class="rl-section rl-section--alt">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center;">Features</h2>
|
||||
<p class="rl-subtext" style="text-align:center;">
|
||||
Everything you need to run your own community video platform.
|
||||
</p>
|
||||
<div class="rl-grid-4">
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">📺</div>
|
||||
<h3>HLS Streaming</h3>
|
||||
<p>Adaptive bitrate streaming ensures smooth playback on any connection speed or device.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">📡</div>
|
||||
<h3>RTMP Ingest</h3>
|
||||
<p>Go live with OBS, Streamlabs, or any RTMP-compatible streaming software. One URL, instant broadcast.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">☁</div>
|
||||
<h3>R2 Storage</h3>
|
||||
<p>Videos stored on Cloudflare R2 for fast, globally distributed delivery with no egress fees.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">🎥</div>
|
||||
<h3>Community Channels</h3>
|
||||
<p>Each space gets its own video channel. Organize content by community, project, or topic.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Technical -->
|
||||
<section class="rl-section">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center;">Technical Details</h2>
|
||||
<div class="rl-card" style="max-width:600px;margin:0 auto;">
|
||||
<ul class="rl-check-list">
|
||||
<li><strong>H.264 / H.265</strong> hardware-accelerated codec support</li>
|
||||
<li><strong>WebM</strong> (VP8/VP9) for open-format video</li>
|
||||
<li><strong>Adaptive bitrate HLS</strong> for smooth playback on any connection</li>
|
||||
<li><strong>HTTP Range requests</strong> for efficient seeking and partial downloads</li>
|
||||
<li><strong>RTMP ingest</strong> compatible with OBS, Streamlabs, and ffmpeg</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CTA -->
|
||||
<section class="rl-section rl-section--alt">
|
||||
<div class="rl-container" style="text-align:center;">
|
||||
<h2 class="rl-heading">Your community's own video platform</h2>
|
||||
<p class="rl-subtext">
|
||||
Try the demo or create a space to start hosting and streaming.
|
||||
</p>
|
||||
<div class="rl-cta-row">
|
||||
<a href="https://demo.rspace.online/rtube" class="rl-cta-primary">Start Streaming</a>
|
||||
<a href="/create-space" class="rl-cta-secondary">Create a Space</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="rl-back">
|
||||
<a href="/">← Back to rSpace</a>
|
||||
</div>`;
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ 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 { S3Client, ListObjectsV2Command, GetObjectCommand, HeadObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3";
|
||||
|
||||
const routes = new Hono();
|
||||
|
|
@ -209,6 +210,7 @@ export const tubeModule: RSpaceModule = {
|
|||
icon: "\u{1F3AC}",
|
||||
description: "Community video hosting & live streaming",
|
||||
routes,
|
||||
landingPage: renderLanding,
|
||||
standaloneDomain: "rtube.online",
|
||||
feeds: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,184 @@
|
|||
/**
|
||||
* rVote landing page — democratic backlog prioritization.
|
||||
*/
|
||||
export function renderLanding(): string {
|
||||
return `
|
||||
<!-- Hero -->
|
||||
<div class="rl-hero">
|
||||
<span class="rl-tagline">rVote</span>
|
||||
<h1 class="rl-heading">Democratic Backlog<br>Prioritization</h1>
|
||||
<p class="rl-subtext">
|
||||
Quadratic conviction voting with time-decay. Surface the ideas your community actually cares about —
|
||||
not just the loudest voices.
|
||||
</p>
|
||||
<div class="rl-cta-row">
|
||||
<a href="https://demo.rspace.online/rvote" class="rl-cta-primary" id="ml-primary">Try the Demo</a>
|
||||
<a href="/create-space" class="rl-cta-secondary">Create a Space</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ELI5 -->
|
||||
<section class="rl-section">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center">Three ideas, one system</h2>
|
||||
<div class="rl-grid-3" style="margin-top:2rem">
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">
|
||||
<span style="font-size:1.25rem;font-weight:700">x²</span>
|
||||
</div>
|
||||
<h3>Quadratic Cost</h3>
|
||||
<p>Each additional vote on the same proposal costs quadratically more credits. One vote = 1 credit, two = 4, three = 9. Prevents plutocratic capture.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="18 15 12 9 6 15"/></svg>
|
||||
</div>
|
||||
<h3>Reddit-style Ranking</h3>
|
||||
<p>Proposals accumulate conviction score from community votes. The most supported ideas float to the top.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
|
||||
</div>
|
||||
<h3>Vote Decay</h3>
|
||||
<p>Votes lose weight over 30–60 days. Stale support fades automatically — no need to manually close polls.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- What is QPR -->
|
||||
<section class="rl-section rl-section--alt">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center">What is Quadratic Priority Ranking?</h2>
|
||||
<div class="rl-grid-2" style="margin-top:2rem">
|
||||
<div class="rl-card">
|
||||
<h3 style="color:#f87171">The Problem</h3>
|
||||
<p>Traditional voting (1 person = 1 vote) lets small, passionate groups dominate.
|
||||
Token-weighted voting lets whales decide everything. Neither reflects genuine community preference.</p>
|
||||
</div>
|
||||
<div class="rl-card">
|
||||
<h3 style="color:#14b8a6">The Solution</h3>
|
||||
<p>Quadratic voting makes strong preferences expensive. You <em>can</em> signal that you care a lot —
|
||||
but it costs quadratically more. This balances intensity of preference with breadth of support.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Vote Cost Calculator -->
|
||||
<section class="rl-section">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center">Vote cost calculator</h2>
|
||||
<p class="rl-subtext" style="text-align:center">The cost of conviction grows quadratically.</p>
|
||||
<div class="rl-card" style="max-width:500px;margin:2rem auto 0">
|
||||
<div style="display:grid;grid-template-columns:repeat(5,1fr);gap:0.5rem;text-align:center">
|
||||
<div>
|
||||
<div style="font-size:1.5rem;font-weight:700;color:#14b8a6">1</div>
|
||||
<div style="font-size:0.7rem;color:#64748b">vote</div>
|
||||
<div style="font-size:0.85rem;font-weight:600;color:#e2e8f0;margin-top:0.25rem">1 credit</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size:1.5rem;font-weight:700;color:#14b8a6">2</div>
|
||||
<div style="font-size:0.7rem;color:#64748b">votes</div>
|
||||
<div style="font-size:0.85rem;font-weight:600;color:#e2e8f0;margin-top:0.25rem">4 credits</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size:1.5rem;font-weight:700;color:#14b8a6">3</div>
|
||||
<div style="font-size:0.7rem;color:#64748b">votes</div>
|
||||
<div style="font-size:0.85rem;font-weight:600;color:#e2e8f0;margin-top:0.25rem">9 credits</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size:1.5rem;font-weight:700;color:#14b8a6">4</div>
|
||||
<div style="font-size:0.7rem;color:#64748b">votes</div>
|
||||
<div style="font-size:0.85rem;font-weight:600;color:#e2e8f0;margin-top:0.25rem">16 credits</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size:1.5rem;font-weight:700;color:#14b8a6">5</div>
|
||||
<div style="font-size:0.7rem;color:#64748b">votes</div>
|
||||
<div style="font-size:0.85rem;font-weight:600;color:#e2e8f0;margin-top:0.25rem">25 credits</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- How it works: 3 stages -->
|
||||
<section class="rl-section rl-section--alt">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center">The lifecycle of a proposal</h2>
|
||||
<div class="rl-grid-3" style="margin-top:2rem">
|
||||
<div class="rl-step">
|
||||
<span class="rl-step__num">1</span>
|
||||
<h3>Ranking (QPR)</h3>
|
||||
<p>Community members spend credits to upvote proposals. Conviction score accumulates with quadratic cost.</p>
|
||||
</div>
|
||||
<div class="rl-step">
|
||||
<span class="rl-step__num">2</span>
|
||||
<h3>Score reaches +100</h3>
|
||||
<p>When a proposal crosses the promotion threshold (default 100), it automatically enters the final vote.</p>
|
||||
</div>
|
||||
<div class="rl-step">
|
||||
<span class="rl-step__num">3</span>
|
||||
<h3>Pass / Fail Vote</h3>
|
||||
<p>A time-limited binary vote (Yes / No / Abstain) decides the outcome. Simple majority wins.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Features -->
|
||||
<section class="rl-section">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center">Built for real governance</h2>
|
||||
<div class="rl-grid-4" style="margin-top:2rem">
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg>
|
||||
</div>
|
||||
<h3>Earn Credits Daily</h3>
|
||||
<p>Every verified member receives a daily credit allowance. No pay-to-play.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg>
|
||||
</div>
|
||||
<h3>Vote Decay</h3>
|
||||
<p>Votes decay linearly from day 30 to day 60. Stale support fades, keeping rankings fresh.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
|
||||
</div>
|
||||
<h3>Sybil Resistant</h3>
|
||||
<p>Passkey authentication via EncryptID. One person, one identity, one credit stream.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="17 1 21 5 17 9"/><path d="M3 11V9a4 4 0 014-4h14"/><polyline points="7 23 3 19 7 15"/><path d="M21 13v2a4 4 0 01-4 4H3"/></svg>
|
||||
</div>
|
||||
<h3>Auto Promotion</h3>
|
||||
<p>Proposals that hit the threshold automatically move to a final pass/fail vote. No admin bottleneck.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CTA -->
|
||||
<section class="rl-section rl-section--alt">
|
||||
<div class="rl-container" style="text-align:center">
|
||||
<h2 class="rl-heading">Ready to prioritize democratically?</h2>
|
||||
<p class="rl-subtext">
|
||||
Create a governance space, submit proposals, and let your community decide what matters most.
|
||||
</p>
|
||||
<div class="rl-cta-row">
|
||||
<a href="https://demo.rspace.online/rvote" class="rl-cta-primary" id="ml-primary">Open rVote</a>
|
||||
<a href="/create-space" class="rl-cta-secondary">Create a Space</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="rl-back">
|
||||
<a href="/">← Back to rSpace</a>
|
||||
</div>`;
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ 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";
|
||||
|
||||
const routes = new Hono();
|
||||
|
||||
|
|
@ -346,6 +347,7 @@ export const voteModule: RSpaceModule = {
|
|||
description: "Conviction voting engine for collaborative governance",
|
||||
routes,
|
||||
standaloneDomain: "rvote.online",
|
||||
landingPage: renderLanding,
|
||||
feeds: [
|
||||
{
|
||||
id: "proposals",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,119 @@
|
|||
/**
|
||||
* rWallet — rich landing page body.
|
||||
* Returned by landingPage() in the module export;
|
||||
* the shell wraps it with header, CSS, and analytics.
|
||||
*/
|
||||
export function renderLanding(): string {
|
||||
const demo = "https://demo.rspace.online/rwallet";
|
||||
|
||||
return `
|
||||
<!-- Hero -->
|
||||
<section class="rl-hero">
|
||||
<span class="rl-tagline">rWallet</span>
|
||||
<h1 class="rl-heading">Community Treasury, Transparent</h1>
|
||||
<p class="rl-subtext">
|
||||
Visualize your Safe multisig across every chain — balances,
|
||||
transactions, and governance — all in one client-side dashboard.
|
||||
No backend database, no custody risk.
|
||||
</p>
|
||||
<div class="rl-cta-row">
|
||||
<a href="${demo}" class="rl-cta-primary" id="ml-primary">View Your Treasury</a>
|
||||
<a href="/create-space" class="rl-cta-secondary">Create a Space</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- How It Works -->
|
||||
<section class="rl-section">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center">How It Works</h2>
|
||||
<div class="rl-grid-3">
|
||||
<div class="rl-step">
|
||||
<div class="rl-step__num">1</div>
|
||||
<h3>Connect Your Safe</h3>
|
||||
<p>Paste any Safe address. rWallet auto-detects which chains it lives on.</p>
|
||||
</div>
|
||||
<div class="rl-step">
|
||||
<div class="rl-step__num">2</div>
|
||||
<h3>View Across Chains</h3>
|
||||
<p>See token balances, USD valuations, and transaction history for every chain in one view.</p>
|
||||
</div>
|
||||
<div class="rl-step">
|
||||
<div class="rl-step__num">3</div>
|
||||
<h3>Govern Together</h3>
|
||||
<p>Review pending multisig transactions and coordinate approvals with your team.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Features -->
|
||||
<section class="rl-section rl-section--alt">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center">Features</h2>
|
||||
<div class="rl-grid-4">
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">⛓</div>
|
||||
<h3>Multi-Chain Support</h3>
|
||||
<p>Ethereum, Polygon, Base, Gnosis, Arbitrum, Optimism, Celo, Avalanche, BSC, and zkSync.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">📋</div>
|
||||
<h3>Transaction History</h3>
|
||||
<p>Browse incoming and outgoing transfers with human-readable labels and USD values.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">🔐</div>
|
||||
<h3>Multisig Governance</h3>
|
||||
<p>See signer thresholds, pending confirmations, and execution status at a glance.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">💰</div>
|
||||
<h3>Token Balances</h3>
|
||||
<p>ERC-20 and native token balances aggregated across all detected chains.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Ecosystem Integration -->
|
||||
<section class="rl-section">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center">Ecosystem Integration</h2>
|
||||
<p class="rl-subtext" style="text-align:center">
|
||||
rWallet connects to other rSpace modules for a complete treasury workflow.
|
||||
</p>
|
||||
<div class="rl-grid-2">
|
||||
<div class="rl-integration">
|
||||
<div class="rl-icon-box">📈</div>
|
||||
<div>
|
||||
<h3><a href="/rfunds" style="color:#14b8a6;text-decoration:none">rFunds</a></h3>
|
||||
<p>Overlay budget categories on your wallet data to see where funds are allocated and how they flow.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rl-integration">
|
||||
<div class="rl-icon-box">☑</div>
|
||||
<div>
|
||||
<h3><a href="/rvote" style="color:#14b8a6;text-decoration:none">rVote</a></h3>
|
||||
<p>Link governance proposals directly to treasury transactions for full accountability.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CTA -->
|
||||
<section class="rl-section rl-section--alt">
|
||||
<div class="rl-container" style="text-align:center">
|
||||
<h2 class="rl-heading">Ready to Open Your Treasury?</h2>
|
||||
<p class="rl-subtext">
|
||||
Paste a Safe address and explore your multichain holdings in seconds.
|
||||
</p>
|
||||
<div class="rl-cta-row">
|
||||
<a href="${demo}" class="rl-cta-primary">View Your Treasury</a>
|
||||
<a href="/create-space" class="rl-cta-secondary">Create a Space</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="rl-back"><a href="/">← Back to rSpace</a></div>`;
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@ import { Hono } from "hono";
|
|||
import { renderShell } from "../../server/shell";
|
||||
import { getModuleInfoList } from "../../shared/module";
|
||||
import type { RSpaceModule } from "../../shared/module";
|
||||
import { renderLanding } from "./landing";
|
||||
|
||||
const routes = new Hono();
|
||||
|
||||
|
|
@ -113,6 +114,7 @@ export const walletModule: RSpaceModule = {
|
|||
description: "Multichain Safe wallet visualization and treasury management",
|
||||
routes,
|
||||
standaloneDomain: "rwallet.online",
|
||||
landingPage: renderLanding,
|
||||
feeds: [
|
||||
{
|
||||
id: "balances",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,127 @@
|
|||
/**
|
||||
* rWork landing page — collective task management.
|
||||
*/
|
||||
export function renderLanding(): string {
|
||||
return `
|
||||
<!-- Hero -->
|
||||
<div class="rl-hero">
|
||||
<span class="rl-tagline">rWork</span>
|
||||
<h1 class="rl-heading">Get things done,<br>together</h1>
|
||||
<p class="rl-subtext">
|
||||
Kanban boards, team spaces, and real-time collaboration — built for groups
|
||||
that share work, not just assign it.
|
||||
</p>
|
||||
<div class="rl-cta-row">
|
||||
<a href="https://demo.rspace.online/rwork" class="rl-cta-primary" id="ml-primary">Try the Demo</a>
|
||||
<a href="/create-space" class="rl-cta-secondary">Create a Space</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- How it works -->
|
||||
<section class="rl-section">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center">How it works</h2>
|
||||
<div class="rl-grid-3" style="margin-top:2rem">
|
||||
<div class="rl-step">
|
||||
<span class="rl-step__num">1</span>
|
||||
<h3>Create a Space</h3>
|
||||
<p>Every workspace lives inside an rSpace — shared by default with your team.</p>
|
||||
</div>
|
||||
<div class="rl-step">
|
||||
<span class="rl-step__num">2</span>
|
||||
<h3>Add Tasks</h3>
|
||||
<p>Create tasks with titles, descriptions, labels, and priorities. Drag them across columns.</p>
|
||||
</div>
|
||||
<div class="rl-step">
|
||||
<span class="rl-step__num">3</span>
|
||||
<h3>Collaborate</h3>
|
||||
<p>Assign, comment, and track progress in real time. Everyone sees the same board.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Features -->
|
||||
<section class="rl-section rl-section--alt">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center">Features</h2>
|
||||
<p class="rl-subtext" style="text-align:center">Everything you need for collaborative task management, nothing you don't.</p>
|
||||
<div class="rl-grid-4" style="margin-top:2rem">
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/></svg>
|
||||
</div>
|
||||
<h3>Kanban Boards</h3>
|
||||
<p>Drag-and-drop columns with customizable statuses. See everything at a glance.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 00-3-3.87"/><path d="M16 3.13a4 4 0 010 7.75"/></svg>
|
||||
</div>
|
||||
<h3>Team Spaces</h3>
|
||||
<p>Each workspace is scoped to a space. Members share boards, tasks, and activity.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/><path d="M3.51 9a9 9 0 0114.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0020.49 15"/></svg>
|
||||
</div>
|
||||
<h3>Real-time Sync</h3>
|
||||
<p>Changes propagate instantly. No refresh needed — everyone stays in sync.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/></svg>
|
||||
</div>
|
||||
<h3>Markdown Native</h3>
|
||||
<p>Task descriptions support full markdown. Write rich content without leaving the board.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/></svg>
|
||||
</div>
|
||||
<h3>Custom Pipelines</h3>
|
||||
<p>Define your own status columns — TODO, In Progress, Review, Done, or whatever fits.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 8A6 6 0 006 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 01-3.46 0"/></svg>
|
||||
</div>
|
||||
<h3>Notifications</h3>
|
||||
<p>Get notified when tasks are assigned, moved, or commented on.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 11 12 14 22 4"/><path d="M21 12v7a2 2 0 01-2 2H5a2 2 0 01-2-2V5a2 2 0 012-2h11"/></svg>
|
||||
</div>
|
||||
<h3>Acceptance Criteria</h3>
|
||||
<p>Define clear done conditions. Check them off as you go.</p>
|
||||
</div>
|
||||
<div class="rl-card rl-card--center">
|
||||
<div class="rl-icon-box">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 014 10 15.3 15.3 0 01-4 10 15.3 15.3 0 01-4-10 15.3 15.3 0 014-10z"/></svg>
|
||||
</div>
|
||||
<h3>rSpace Ecosystem</h3>
|
||||
<p>Tasks link to rVote proposals, rCal events, rFunds budgets, and more.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CTA -->
|
||||
<section class="rl-section">
|
||||
<div class="rl-container" style="text-align:center">
|
||||
<h2 class="rl-heading">Ready to collaborate?</h2>
|
||||
<p class="rl-subtext">
|
||||
Create a workspace, invite your team, and start shipping together.
|
||||
</p>
|
||||
<div class="rl-cta-row">
|
||||
<a href="https://demo.rspace.online/rwork" class="rl-cta-primary" id="ml-primary">Open rWork</a>
|
||||
<a href="/create-space" class="rl-cta-secondary">Create a Space</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="rl-back">
|
||||
<a href="/">← Back to rSpace</a>
|
||||
</div>`;
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ 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";
|
||||
|
||||
const routes = new Hono();
|
||||
|
||||
|
|
@ -236,6 +237,7 @@ export const workModule: RSpaceModule = {
|
|||
description: "Kanban workspace boards for collaborative task management",
|
||||
routes,
|
||||
standaloneDomain: "rwork.online",
|
||||
landingPage: renderLanding,
|
||||
feeds: [
|
||||
{
|
||||
id: "task-activity",
|
||||
|
|
|
|||
|
|
@ -1081,14 +1081,23 @@ const server = Bun.serve<WSData>({
|
|||
const mod = allModules.find((m) => m.id === firstSegment);
|
||||
if (mod) {
|
||||
if (pathSegments.length === 1) {
|
||||
// Try proxying the rich standalone landing page
|
||||
// 1. Check for inline rich landing page
|
||||
if (mod.landingPage) {
|
||||
const html = renderModuleLanding({
|
||||
module: mod,
|
||||
modules: getModuleInfoList(),
|
||||
bodyHTML: mod.landingPage(),
|
||||
});
|
||||
return new Response(html, { headers: { "Content-Type": "text/html; charset=utf-8" } });
|
||||
}
|
||||
// 2. Try proxying the rich standalone landing page
|
||||
const proxyHtml = await fetchLandingPage(mod, getModuleInfoList());
|
||||
if (proxyHtml) {
|
||||
return new Response(proxyHtml, {
|
||||
headers: { "Content-Type": "text/html; charset=utf-8" },
|
||||
});
|
||||
}
|
||||
// Fallback to generic landing page
|
||||
// 3. Fallback to generic landing page
|
||||
const html = renderModuleLanding({
|
||||
module: mod,
|
||||
modules: getModuleInfoList(),
|
||||
|
|
|
|||
220
server/shell.ts
220
server/shell.ts
|
|
@ -423,13 +423,36 @@ export interface ModuleLandingOptions {
|
|||
modules: ModuleInfo[];
|
||||
/** Theme */
|
||||
theme?: "dark" | "light";
|
||||
/** Rich body HTML from a module's landing.ts (replaces generic hero) */
|
||||
bodyHTML?: string;
|
||||
}
|
||||
|
||||
export function renderModuleLanding(opts: ModuleLandingOptions): string {
|
||||
const { module: mod, modules, theme = "dark" } = opts;
|
||||
const { module: mod, modules, theme = "dark", bodyHTML } = opts;
|
||||
const moduleListJSON = JSON.stringify(modules);
|
||||
const demoUrl = `https://demo.rspace.online/${mod.id}`;
|
||||
|
||||
const cssBlock = bodyHTML
|
||||
? `<style>${MODULE_LANDING_CSS}</style>\n <style>${RICH_LANDING_CSS}</style>`
|
||||
: `<style>${MODULE_LANDING_CSS}</style>`;
|
||||
|
||||
const bodyContent = bodyHTML
|
||||
? bodyHTML
|
||||
: `<div class="ml-hero">
|
||||
<div class="ml-container">
|
||||
<span class="ml-icon">${mod.icon}</span>
|
||||
<h1 class="ml-name">${escapeHtml(mod.name)}</h1>
|
||||
<p class="ml-desc">${escapeHtml(mod.description)}</p>
|
||||
<div class="ml-ctas">
|
||||
<a href="${demoUrl}" class="ml-cta-primary" id="ml-primary">Try Demo</a>
|
||||
<a href="/create-space" class="ml-cta-secondary">Create a Space</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-back">
|
||||
<a href="/">← Back to rSpace</a>
|
||||
</div>`;
|
||||
|
||||
return `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
|
|
@ -438,7 +461,7 @@ export function renderModuleLanding(opts: ModuleLandingOptions): string {
|
|||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>${mod.icon}</text></svg>">
|
||||
<title>${escapeHtml(mod.name)} — rSpace</title>
|
||||
<link rel="stylesheet" href="/shell.css">
|
||||
<style>${MODULE_LANDING_CSS}</style>
|
||||
${cssBlock}
|
||||
<script defer src="https://rdata.online/collect.js" data-website-id="6ee7917b-0ed7-44cb-a4c8-91037638526b"></script>
|
||||
</head>
|
||||
<body data-theme="${theme}">
|
||||
|
|
@ -452,20 +475,7 @@ export function renderModuleLanding(opts: ModuleLandingOptions): string {
|
|||
<rstack-identity></rstack-identity>
|
||||
</div>
|
||||
</header>
|
||||
<div class="ml-hero">
|
||||
<div class="ml-container">
|
||||
<span class="ml-icon">${mod.icon}</span>
|
||||
<h1 class="ml-name">${escapeHtml(mod.name)}</h1>
|
||||
<p class="ml-desc">${escapeHtml(mod.description)}</p>
|
||||
<div class="ml-ctas">
|
||||
<a href="${demoUrl}" class="ml-cta-primary" id="ml-primary">Try Demo</a>
|
||||
<a href="/create-space" class="ml-cta-secondary">Create a Space</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-back">
|
||||
<a href="/">← Back to rSpace</a>
|
||||
</div>
|
||||
${bodyContent}
|
||||
<script type="module">
|
||||
import '/shell.js';
|
||||
document.querySelector('rstack-app-switcher')?.setModules(${moduleListJSON});
|
||||
|
|
@ -529,6 +539,184 @@ body {
|
|||
@media (max-width: 600px) { .ml-name { font-size: 2rem; } .ml-icon { font-size: 3rem; } }
|
||||
`;
|
||||
|
||||
const RICH_LANDING_CSS = `
|
||||
/* ── Rich Landing Page Utilities ── */
|
||||
.rl-section {
|
||||
border-top: 1px solid rgba(255,255,255,0.06);
|
||||
padding: 4rem 1.5rem;
|
||||
}
|
||||
.rl-section--alt { background: rgba(255,255,255,0.015); }
|
||||
.rl-container { max-width: 1100px; margin: 0 auto; }
|
||||
.rl-hero {
|
||||
text-align: center; padding: 5rem 1.5rem 3rem;
|
||||
max-width: 820px; margin: 0 auto;
|
||||
}
|
||||
.rl-tagline {
|
||||
display: inline-block; font-size: 0.7rem; font-weight: 700;
|
||||
letter-spacing: 0.12em; text-transform: uppercase;
|
||||
color: #14b8a6; background: rgba(20,184,166,0.1);
|
||||
border: 1px solid rgba(20,184,166,0.2);
|
||||
padding: 0.35rem 1rem; border-radius: 9999px; margin-bottom: 1.5rem;
|
||||
}
|
||||
.rl-heading {
|
||||
font-size: 2rem; font-weight: 700; line-height: 1.15;
|
||||
margin-bottom: 0.75rem; letter-spacing: -0.01em;
|
||||
background: linear-gradient(135deg, #14b8a6, #22d3ee);
|
||||
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
.rl-hero .rl-heading { font-size: 2.5rem; }
|
||||
@media (min-width: 640px) { .rl-hero .rl-heading { font-size: 3rem; } }
|
||||
.rl-subtext {
|
||||
font-size: 1.05rem; color: #94a3b8; line-height: 1.65;
|
||||
max-width: 640px; margin: 0 auto 2rem;
|
||||
}
|
||||
.rl-hero .rl-subtext { font-size: 1.15rem; }
|
||||
|
||||
/* Grids */
|
||||
.rl-grid-2 { display: grid; grid-template-columns: 1fr; gap: 1.25rem; }
|
||||
.rl-grid-3 { display: grid; grid-template-columns: 1fr; gap: 1.25rem; }
|
||||
.rl-grid-4 { display: grid; grid-template-columns: 1fr; gap: 1.25rem; }
|
||||
@media (min-width: 640px) {
|
||||
.rl-grid-2 { grid-template-columns: repeat(2, 1fr); }
|
||||
.rl-grid-3 { grid-template-columns: repeat(3, 1fr); }
|
||||
.rl-grid-4 { grid-template-columns: repeat(2, 1fr); }
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.rl-grid-4 { grid-template-columns: repeat(4, 1fr); }
|
||||
}
|
||||
|
||||
/* Card */
|
||||
.rl-card {
|
||||
background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.06);
|
||||
border-radius: 1rem; padding: 1.75rem;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
.rl-card:hover { border-color: rgba(20,184,166,0.3); }
|
||||
.rl-card h3 { font-size: 0.95rem; font-weight: 600; color: #e2e8f0; margin-bottom: 0.5rem; }
|
||||
.rl-card p { font-size: 0.875rem; color: #94a3b8; line-height: 1.6; }
|
||||
.rl-card--center { text-align: center; }
|
||||
|
||||
/* Step circles */
|
||||
.rl-step {
|
||||
display: flex; flex-direction: column; align-items: center; text-align: center;
|
||||
}
|
||||
.rl-step__num {
|
||||
width: 2.5rem; height: 2.5rem; border-radius: 9999px;
|
||||
background: rgba(20,184,166,0.1); color: #14b8a6;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 0.8rem; font-weight: 700; margin-bottom: 0.75rem;
|
||||
}
|
||||
.rl-step h3 { font-size: 0.95rem; font-weight: 600; color: #e2e8f0; margin-bottom: 0.25rem; }
|
||||
.rl-step p { font-size: 0.82rem; color: #94a3b8; line-height: 1.55; }
|
||||
|
||||
/* CTA row */
|
||||
.rl-cta-row {
|
||||
display: flex; gap: 0.75rem; justify-content: center; flex-wrap: wrap;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
.rl-cta-primary {
|
||||
display: inline-block; padding: 0.8rem 2rem; border-radius: 0.5rem;
|
||||
background: linear-gradient(135deg, #14b8a6, #0d9488);
|
||||
color: white; font-size: 0.95rem; font-weight: 600;
|
||||
text-decoration: none; transition: transform 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
.rl-cta-primary:hover { transform: translateY(-2px); box-shadow: 0 8px 20px rgba(20,184,166,0.3); }
|
||||
.rl-cta-secondary {
|
||||
display: inline-block; padding: 0.8rem 2rem; border-radius: 0.5rem;
|
||||
background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.15);
|
||||
color: #94a3b8; font-size: 0.95rem; font-weight: 600;
|
||||
text-decoration: none; transition: transform 0.2s, border-color 0.2s, color 0.2s;
|
||||
}
|
||||
.rl-cta-secondary:hover { transform: translateY(-2px); border-color: rgba(255,255,255,0.35); color: white; }
|
||||
|
||||
/* Check list */
|
||||
.rl-check-list { list-style: none; padding: 0; margin: 0; }
|
||||
.rl-check-list li {
|
||||
display: flex; align-items: flex-start; gap: 0.5rem;
|
||||
font-size: 0.875rem; color: #94a3b8; line-height: 1.55;
|
||||
padding: 0.35rem 0;
|
||||
}
|
||||
.rl-check-list li::before {
|
||||
content: "✓"; color: #14b8a6; font-weight: 700; flex-shrink: 0; margin-top: 0.05em;
|
||||
}
|
||||
.rl-check-list li strong { color: #e2e8f0; font-weight: 600; }
|
||||
|
||||
/* Badge */
|
||||
.rl-badge {
|
||||
display: inline-block; font-size: 0.65rem; font-weight: 700;
|
||||
color: white; background: #14b8a6;
|
||||
padding: 0.15rem 0.5rem; border-radius: 9999px;
|
||||
}
|
||||
|
||||
/* Divider */
|
||||
.rl-divider {
|
||||
display: flex; align-items: center; gap: 0.75rem; margin: 1.5rem 0;
|
||||
}
|
||||
.rl-divider::before, .rl-divider::after {
|
||||
content: ""; flex: 1; height: 1px; background: rgba(255,255,255,0.06);
|
||||
}
|
||||
.rl-divider span { font-size: 0.75rem; color: #64748b; white-space: nowrap; }
|
||||
|
||||
/* Icon box */
|
||||
.rl-icon-box {
|
||||
width: 3rem; height: 3rem; border-radius: 0.75rem;
|
||||
background: rgba(20,184,166,0.12); color: #14b8a6;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 1.5rem; margin-bottom: 1rem;
|
||||
}
|
||||
.rl-card--center .rl-icon-box { margin: 0 auto 1rem; }
|
||||
|
||||
/* Integration (2-col with icon) */
|
||||
.rl-integration {
|
||||
display: flex; align-items: flex-start; gap: 1rem;
|
||||
background: rgba(20,184,166,0.04); border: 1px solid rgba(20,184,166,0.15);
|
||||
border-radius: 1rem; padding: 1.5rem;
|
||||
}
|
||||
.rl-integration h3 { font-size: 0.95rem; font-weight: 600; color: #e2e8f0; margin-bottom: 0.35rem; }
|
||||
.rl-integration p { font-size: 0.85rem; color: #94a3b8; line-height: 1.55; }
|
||||
|
||||
/* Back link */
|
||||
.rl-back { padding: 2rem 0 3rem; text-align: center; }
|
||||
.rl-back a { font-size: 0.85rem; color: #64748b; text-decoration: none; transition: color 0.2s; }
|
||||
.rl-back a:hover { color: #e2e8f0; }
|
||||
|
||||
/* Progress bar */
|
||||
.rl-progress { height: 0.5rem; border-radius: 9999px; background: rgba(255,255,255,0.06); overflow: hidden; }
|
||||
.rl-progress__fill { height: 100%; border-radius: 9999px; background: #14b8a6; }
|
||||
|
||||
/* Tier row */
|
||||
.rl-tier {
|
||||
display: flex; gap: 0.5rem; margin: 1rem 0;
|
||||
}
|
||||
.rl-tier__item {
|
||||
flex: 1; text-align: center; border-radius: 0.5rem;
|
||||
border: 1px solid rgba(255,255,255,0.06); padding: 0.5rem; font-size: 0.75rem;
|
||||
}
|
||||
.rl-tier__item--active {
|
||||
border-color: rgba(20,184,166,0.4); background: rgba(20,184,166,0.05); color: #14b8a6;
|
||||
}
|
||||
.rl-tier__item--active strong { color: #14b8a6; }
|
||||
|
||||
/* Temporal zoom bar */
|
||||
.rl-zoom-bar { display: flex; flex-direction: column; gap: 0.5rem; }
|
||||
.rl-zoom-bar__row { display: flex; align-items: center; gap: 0.75rem; }
|
||||
.rl-zoom-bar__label { font-size: 0.7rem; color: #64748b; width: 1.2rem; text-align: right; font-family: monospace; }
|
||||
.rl-zoom-bar__bar {
|
||||
height: 1.5rem; border-radius: 0.375rem; background: rgba(99,102,241,0.15);
|
||||
display: flex; align-items: center; padding: 0 0.75rem;
|
||||
}
|
||||
.rl-zoom-bar__name { font-size: 0.75rem; font-weight: 600; color: #e2e8f0; white-space: nowrap; }
|
||||
.rl-zoom-bar__span { font-size: 0.6rem; color: #64748b; margin-left: auto; white-space: nowrap; }
|
||||
|
||||
/* Responsive helpers */
|
||||
@media (max-width: 600px) {
|
||||
.rl-hero { padding: 3rem 1rem 2rem; }
|
||||
.rl-hero .rl-heading { font-size: 2rem; }
|
||||
.rl-section { padding: 2.5rem 1rem; }
|
||||
}
|
||||
`;
|
||||
|
||||
export function escapeHtml(s: string): string {
|
||||
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,6 +33,8 @@ export interface RSpaceModule {
|
|||
onSpaceDelete?: (spaceSlug: string) => Promise<void>;
|
||||
/** If true, module is hidden from app switcher (still has routes) */
|
||||
hidden?: boolean;
|
||||
/** Optional: render rich landing page body HTML */
|
||||
landingPage?: () => string;
|
||||
}
|
||||
|
||||
/** Registry of all loaded modules */
|
||||
|
|
@ -60,6 +62,7 @@ export interface ModuleInfo {
|
|||
feeds?: FeedDefinition[];
|
||||
acceptsFeeds?: FlowKind[];
|
||||
hidden?: boolean;
|
||||
hasLandingPage?: boolean;
|
||||
}
|
||||
|
||||
export function getModuleInfoList(): ModuleInfo[] {
|
||||
|
|
@ -73,5 +76,6 @@ export function getModuleInfoList(): ModuleInfo[] {
|
|||
...(m.standaloneDomain ? { standaloneDomain: m.standaloneDomain } : {}),
|
||||
...(m.feeds ? { feeds: m.feeds } : {}),
|
||||
...(m.acceptsFeeds ? { acceptsFeeds: m.acceptsFeeds } : {}),
|
||||
...(m.landingPage ? { hasLandingPage: true } : {}),
|
||||
}));
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue