feat(rcal): replace static demo with interactive folk-calendar-view
The demo page was a frozen July 2026 static HTML grid with no real functionality. Now embeds <folk-calendar-view space="demo"> which provides full month/week/day views, navigation, lunar overlay, source filtering, event modals, keyboard shortcuts, and 37 demo events. Keeps hero, temporal zoom showcase, feature cards, ecosystem integrations, and CTA sections around the interactive component. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
317bc46de6
commit
4979c3d80c
|
|
@ -1,46 +1,12 @@
|
||||||
/**
|
/**
|
||||||
* rCal demo page — server-rendered HTML body.
|
* rCal demo page — server-rendered HTML body.
|
||||||
*
|
*
|
||||||
* Static July 2026 calendar grid with Alpine Explorer trip events,
|
* Embeds the full <folk-calendar-view space="demo"> component for
|
||||||
* tab switching (Temporal/Spatial/Lunar/Context), zoom panel,
|
* real interactivity (month/week/day views, navigation, lunar overlay,
|
||||||
* and feature cards. Entirely local state, no WebSocket.
|
* source filtering, event modals, keyboard shortcuts) plus showcase
|
||||||
|
* sections explaining the rCal vision.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* ─── Event Data ──────────────────────────────────────────── */
|
|
||||||
|
|
||||||
interface CalEvent {
|
|
||||||
day: number;
|
|
||||||
emoji: string;
|
|
||||||
label: string;
|
|
||||||
color: string;
|
|
||||||
bg: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const TRIP_EVENTS: CalEvent[] = [
|
|
||||||
{ day: 6, emoji: "\u2708\uFE0F", label: "Arrive Chamonix", color: "#2dd4bf", bg: "rgba(20,184,166,0.15)" },
|
|
||||||
{ day: 7, emoji: "\u{1F97E}", label: "Lac Blanc Hike", color: "#34d399", bg: "rgba(16,185,129,0.15)" },
|
|
||||||
{ day: 8, emoji: "\u{1F9D7}", label: "Aiguille du Midi", color: "#fbbf24", bg: "rgba(245,158,11,0.15)" },
|
|
||||||
{ day: 9, emoji: "\u{1F97E}", label: "Mer de Glace", color: "#34d399", bg: "rgba(16,185,129,0.15)" },
|
|
||||||
{ day: 10, emoji: "\u{1F682}", label: "Train to Zermatt", color: "#22d3ee", bg: "rgba(6,182,212,0.15)" },
|
|
||||||
{ day: 11, emoji: "\u{1F97E}", label: "Five Lakes Walk", color: "#34d399", bg: "rgba(16,185,129,0.15)" },
|
|
||||||
{ day: 12, emoji: "\u26F7", label: "Glacier Paradise", color: "#fbbf24", bg: "rgba(245,158,11,0.15)" },
|
|
||||||
{ day: 13, emoji: "\u{1F3DB}", label: "Alpine Museum", color: "#a78bfa", bg: "rgba(139,92,246,0.15)" },
|
|
||||||
{ day: 14, emoji: "\u{1F68C}", label: "Bus to Dolomites", color: "#22d3ee", bg: "rgba(6,182,212,0.15)" },
|
|
||||||
{ day: 15, emoji: "\u{1F97E}", label: "Tre Cime Circuit", color: "#34d399", bg: "rgba(16,185,129,0.15)" },
|
|
||||||
{ day: 16, emoji: "\u{1FA82}", label: "Paragliding", color: "#fbbf24", bg: "rgba(245,158,11,0.15)" },
|
|
||||||
{ day: 17, emoji: "\u{1F6F6}", label: "Lago di Braies", color: "#fbbf24", bg: "rgba(245,158,11,0.15)" },
|
|
||||||
{ day: 18, emoji: "\u{1F97E}", label: "Seceda Ridge", color: "#34d399", bg: "rgba(16,185,129,0.15)" },
|
|
||||||
{ day: 19, emoji: "\u{1F4F8}", label: "Rest & Photos", color: "#94a3b8", bg: "rgba(100,116,139,0.15)" },
|
|
||||||
{ day: 20, emoji: "\u2708\uFE0F", label: "Depart", color: "#2dd4bf", bg: "rgba(20,184,166,0.15)" },
|
|
||||||
];
|
|
||||||
|
|
||||||
const TABS = ["Temporal", "Spatial", "Lunar", "Context"];
|
|
||||||
|
|
||||||
const ZOOM_LEVELS = [
|
|
||||||
"Era", "Century", "Decade", "Year", "Quarter",
|
|
||||||
"Month", "Week", "Day", "Hour", "Minute",
|
|
||||||
];
|
|
||||||
|
|
||||||
const FEATURES = [
|
const FEATURES = [
|
||||||
{
|
{
|
||||||
icon: "\u{1F50D}",
|
icon: "\u{1F50D}",
|
||||||
|
|
@ -64,62 +30,21 @@ const FEATURES = [
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const LEGEND = [
|
const INTEGRATIONS = [
|
||||||
{ color: "#2dd4bf", label: "Travel" },
|
{ icon: "\u{1F5FA}", name: "rTrips", desc: "Travel itineraries surface as calendar events with departure/arrival times and locations." },
|
||||||
{ color: "#34d399", label: "Hike" },
|
{ icon: "\u{1F30D}", name: "rMaps", desc: "Events appear on the map. Zoom the map and the calendar filters to show only visible events." },
|
||||||
{ color: "#fbbf24", label: "Adventure" },
|
{ icon: "\u{1F465}", name: "rNetwork", desc: "See availability across your network. Coordinate meetings without back-and-forth." },
|
||||||
{ color: "#22d3ee", label: "Transit" },
|
{ icon: "\u{1F4DD}", name: "rNotes", desc: "Link notes to calendar events. Meeting agendas, daily journals, and retrospective logs." },
|
||||||
{ color: "#a78bfa", label: "Culture" },
|
{ icon: "\u{1F4B0}", name: "rFunds", desc: "Budget reviews, treasury flows, and governance votes appear on the calendar timeline." },
|
||||||
{ color: "#94a3b8", label: "Rest" },
|
{ icon: "\u2615\uFE0F", name: "rSpace", desc: "Each space has its own calendar layer. Nest calendars across spaces for cross-community coordination." },
|
||||||
];
|
];
|
||||||
|
|
||||||
/* ─── Helpers ─────────────────────────────────────────────── */
|
const ZOOM_LEVELS = [
|
||||||
|
"Era", "Century", "Decade", "Year", "Quarter",
|
||||||
function eventForDay(day: number): CalEvent | undefined {
|
"Month", "Week", "Day", "Hour", "Minute",
|
||||||
return TRIP_EVENTS.find((e) => e.day === day);
|
];
|
||||||
}
|
|
||||||
|
|
||||||
function isTripDay(day: number): boolean {
|
|
||||||
return day >= 6 && day <= 20;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ─── Render ──────────────────────────────────────────────── */
|
|
||||||
|
|
||||||
export function renderDemo(): string {
|
export function renderDemo(): string {
|
||||||
// July 2026: starts on Wednesday (offset 2 for Mon-based grid), 31 days
|
|
||||||
const firstDayOffset = 2; // Mon=0, Tue=1, Wed=2
|
|
||||||
const totalDays = 31;
|
|
||||||
const dayNames = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
|
|
||||||
|
|
||||||
// Build calendar cells
|
|
||||||
const calendarCells: string[] = [];
|
|
||||||
|
|
||||||
// Empty offset cells
|
|
||||||
for (let i = 0; i < firstDayOffset; i++) {
|
|
||||||
calendarCells.push(`<div class="rcal-cell rcal-cell--empty"></div>`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Day cells
|
|
||||||
for (let d = 1; d <= totalDays; d++) {
|
|
||||||
const ev = eventForDay(d);
|
|
||||||
const trip = isTripDay(d);
|
|
||||||
const todayClass = d === 15 ? " rcal-cell--today" : "";
|
|
||||||
const tripClass = trip ? " rcal-cell--trip" : "";
|
|
||||||
|
|
||||||
let pill = "";
|
|
||||||
if (ev) {
|
|
||||||
pill = `<div class="rcal-pill" style="background:${ev.bg};color:${ev.color};border:1px solid ${ev.color}22;">
|
|
||||||
<span class="rcal-pill__emoji">${ev.emoji}</span>
|
|
||||||
<span class="rcal-pill__label">${ev.label}</span>
|
|
||||||
</div>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
calendarCells.push(`<div class="rcal-cell${tripClass}${todayClass}">
|
|
||||||
<span class="rcal-cell__num${trip ? " rcal-cell__num--trip" : ""}">${d}</span>
|
|
||||||
${pill}
|
|
||||||
</div>`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="rd-root" style="--rd-accent-from:#6366f1; --rd-accent-to:#a78bfa;">
|
<div class="rd-root" style="--rd-accent-from:#6366f1; --rd-accent-to:#a78bfa;">
|
||||||
|
|
||||||
|
|
@ -129,7 +54,7 @@ export function renderDemo(): string {
|
||||||
Multi-Dimensional Calendar
|
Multi-Dimensional Calendar
|
||||||
</div>
|
</div>
|
||||||
<h1>rCal Demo</h1>
|
<h1>rCal Demo</h1>
|
||||||
<p class="rd-subtitle">Multi-dimensional calendar with temporal zoom</p>
|
<p class="rd-subtitle">Temporal coordination with lunar cycles, spatial context, and multi-scale zoom</p>
|
||||||
<div class="rd-meta">
|
<div class="rd-meta">
|
||||||
<span>\u{1F50D} Temporal Zoom</span>
|
<span>\u{1F50D} Temporal Zoom</span>
|
||||||
<span style="color:#475569">|</span>
|
<span style="color:#475569">|</span>
|
||||||
|
|
@ -141,62 +66,35 @@ export function renderDemo(): string {
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- ── Calendar Section ── -->
|
<!-- ── Interactive Calendar ── -->
|
||||||
<section class="rd-section rd-section--narrow">
|
<section class="rd-section rd-section--narrow">
|
||||||
|
<div class="rd-card" style="padding:0;overflow:hidden;">
|
||||||
<!-- Header bar -->
|
<folk-calendar-view space="demo"></folk-calendar-view>
|
||||||
<div class="rd-card" style="margin-bottom:0;">
|
</div>
|
||||||
<div style="display:flex;align-items:center;justify-content:space-between;padding:1rem 1.25rem;border-bottom:1px solid rgba(51,65,85,0.3);flex-wrap:wrap;gap:0.75rem;">
|
<div style="text-align:center;padding:0.75rem 0;font-size:0.8rem;color:#64748b;">
|
||||||
<h2 style="font-size:1.25rem;font-weight:700;color:#f1f5f9;margin:0;display:flex;align-items:center;gap:0.5rem;">
|
<kbd style="background:#1e293b;padding:2px 6px;border-radius:4px;border:1px solid #334155;font-size:0.75rem;">1</kbd>
|
||||||
\u{1F4C5} July 2026
|
<kbd style="background:#1e293b;padding:2px 6px;border-radius:4px;border:1px solid #334155;font-size:0.75rem;">2</kbd>
|
||||||
</h2>
|
<kbd style="background:#1e293b;padding:2px 6px;border-radius:4px;border:1px solid #334155;font-size:0.75rem;">3</kbd>
|
||||||
<div style="display:flex;gap:0.25rem;" id="rcal-tabs">
|
Day / Week / Month \u00B7
|
||||||
${TABS.map(
|
<kbd style="background:#1e293b;padding:2px 6px;border-radius:4px;border:1px solid #334155;font-size:0.75rem;">\u2190</kbd>
|
||||||
(tab, i) => `<button data-cal-tab="${tab.toLowerCase()}" style="
|
<kbd style="background:#1e293b;padding:2px 6px;border-radius:4px;border:1px solid #334155;font-size:0.75rem;">\u2192</kbd>
|
||||||
padding:0.375rem 0.875rem;
|
Navigate \u00B7
|
||||||
border-radius:0.5rem;
|
<kbd style="background:#1e293b;padding:2px 6px;border-radius:4px;border:1px solid #334155;font-size:0.75rem;">L</kbd>
|
||||||
font-size:0.8rem;
|
Lunar \u00B7
|
||||||
font-weight:500;
|
<kbd style="background:#1e293b;padding:2px 6px;border-radius:4px;border:1px solid #334155;font-size:0.75rem;">T</kbd>
|
||||||
border:none;
|
Today
|
||||||
cursor:pointer;
|
|
||||||
transition:all 0.15s;
|
|
||||||
${i === 0 ? "background:rgba(99,102,241,0.15);color:#818cf8;" : "background:transparent;color:#94a3b8;"}
|
|
||||||
">${tab}</button>`,
|
|
||||||
).join("\n ")}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Day header row -->
|
|
||||||
<div class="rcal-grid rcal-grid--header">
|
|
||||||
${dayNames.map((d) => `<div class="rcal-day-header">${d}</div>`).join("\n ")}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Calendar grid -->
|
|
||||||
<div class="rcal-grid">
|
|
||||||
${calendarCells.join("\n ")}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Legend -->
|
|
||||||
<div style="display:flex;align-items:center;gap:1rem;padding:0.75rem 1.25rem;border-top:1px solid rgba(51,65,85,0.3);flex-wrap:wrap;">
|
|
||||||
<span style="font-size:0.75rem;color:#64748b;font-weight:500;">Legend:</span>
|
|
||||||
${LEGEND.map(
|
|
||||||
(l) => `<span style="display:flex;align-items:center;gap:0.375rem;font-size:0.75rem;color:#94a3b8;">
|
|
||||||
<span style="width:0.5rem;height:0.5rem;border-radius:9999px;background:${l.color};display:inline-block;"></span>
|
|
||||||
${l.label}
|
|
||||||
</span>`,
|
|
||||||
).join("\n ")}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- ── Zoom Panel ── -->
|
<!-- ── Temporal Zoom Showcase ── -->
|
||||||
<section class="rd-section rd-section--narrow">
|
<section class="rd-section rd-section--narrow">
|
||||||
<div class="rd-card" style="padding:1.5rem;">
|
<div class="rd-card" style="padding:1.5rem;">
|
||||||
<h2 style="font-size:1.125rem;font-weight:600;color:#f1f5f9;margin:0 0 1rem;display:flex;align-items:center;gap:0.5rem;">
|
<h2 style="font-size:1.125rem;font-weight:600;color:#f1f5f9;margin:0 0 0.75rem;display:flex;align-items:center;gap:0.5rem;">
|
||||||
\u{1F50D} Temporal Zoom
|
\u{1F50D} Temporal Zoom
|
||||||
</h2>
|
</h2>
|
||||||
<p style="font-size:0.875rem;color:#94a3b8;margin:0 0 1rem;">
|
<p style="font-size:0.875rem;color:#94a3b8;margin:0 0 1rem;line-height:1.5;">
|
||||||
Navigate across temporal granularities. The calendar grid adapts at each zoom level.
|
Navigate across 10 temporal granularities. The calendar adapts its grid at each level —
|
||||||
|
from geological eras to individual minutes.
|
||||||
</p>
|
</p>
|
||||||
<div style="display:flex;flex-wrap:wrap;gap:0.5rem;">
|
<div style="display:flex;flex-wrap:wrap;gap:0.5rem;">
|
||||||
${ZOOM_LEVELS.map(
|
${ZOOM_LEVELS.map(
|
||||||
|
|
@ -218,7 +116,7 @@ export function renderDemo(): string {
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- ── Features ── -->
|
<!-- ── Core Concepts ── -->
|
||||||
<section class="rd-section">
|
<section class="rd-section">
|
||||||
<div class="rd-grid rd-grid--2">
|
<div class="rd-grid rd-grid--2">
|
||||||
${FEATURES.map(
|
${FEATURES.map(
|
||||||
|
|
@ -232,6 +130,23 @@ export function renderDemo(): string {
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- ── Ecosystem Integrations ── -->
|
||||||
|
<section class="rd-section">
|
||||||
|
<h2 style="text-align:center;font-size:1.25rem;font-weight:700;color:#f1f5f9;margin:0 0 1.5rem;">
|
||||||
|
r* Ecosystem Integrations
|
||||||
|
</h2>
|
||||||
|
<div class="rd-grid rd-grid--3">
|
||||||
|
${INTEGRATIONS.map(
|
||||||
|
(i) => `
|
||||||
|
<div class="rd-card" style="padding:1.25rem;">
|
||||||
|
<div style="font-size:1.5rem;margin-bottom:0.5rem;">${i.icon}</div>
|
||||||
|
<h3 style="font-size:0.875rem;font-weight:600;color:#e2e8f0;margin:0 0 0.375rem;">${i.name}</h3>
|
||||||
|
<p style="font-size:0.8rem;color:#94a3b8;margin:0;line-height:1.4;">${i.desc}</p>
|
||||||
|
</div>`,
|
||||||
|
).join("")}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<!-- ── CTA ── -->
|
<!-- ── CTA ── -->
|
||||||
<section class="rd-section rd-section--narrow">
|
<section class="rd-section rd-section--narrow">
|
||||||
<div class="rd-cta">
|
<div class="rd-cta">
|
||||||
|
|
@ -246,99 +161,5 @@ export function renderDemo(): string {
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
</div>
|
</div>`;
|
||||||
|
|
||||||
<style>
|
|
||||||
/* ── rCal demo grid ── */
|
|
||||||
.rcal-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(7, 1fr);
|
|
||||||
gap: 1px;
|
|
||||||
background: rgba(51,65,85,0.2);
|
|
||||||
padding: 0 1px 1px;
|
|
||||||
}
|
|
||||||
.rcal-grid--header {
|
|
||||||
gap: 0;
|
|
||||||
padding: 0;
|
|
||||||
background: transparent;
|
|
||||||
border-bottom: 1px solid rgba(51,65,85,0.3);
|
|
||||||
}
|
|
||||||
.rcal-day-header {
|
|
||||||
padding: 0.5rem 0;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #64748b;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.05em;
|
|
||||||
}
|
|
||||||
.rcal-cell {
|
|
||||||
min-height: 3.5rem;
|
|
||||||
padding: 0.375rem;
|
|
||||||
background: rgba(15,23,42,0.6);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.25rem;
|
|
||||||
transition: background 0.15s;
|
|
||||||
}
|
|
||||||
.rcal-cell:hover {
|
|
||||||
background: rgba(30,41,59,0.8);
|
|
||||||
}
|
|
||||||
.rcal-cell--empty {
|
|
||||||
background: rgba(15,23,42,0.3);
|
|
||||||
}
|
|
||||||
.rcal-cell--trip {
|
|
||||||
background: rgba(99,102,241,0.04);
|
|
||||||
}
|
|
||||||
.rcal-cell--today {
|
|
||||||
outline: 2px solid rgba(99,102,241,0.5);
|
|
||||||
outline-offset: -2px;
|
|
||||||
background: rgba(99,102,241,0.08);
|
|
||||||
}
|
|
||||||
.rcal-cell__num {
|
|
||||||
font-size: 0.75rem;
|
|
||||||
font-weight: 500;
|
|
||||||
color: #64748b;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
.rcal-cell__num--trip {
|
|
||||||
color: #a5b4fc;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
.rcal-pill {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.25rem;
|
|
||||||
padding: 0.125rem 0.375rem;
|
|
||||||
border-radius: 0.25rem;
|
|
||||||
font-size: 0.625rem;
|
|
||||||
line-height: 1.2;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
.rcal-pill__emoji {
|
|
||||||
flex-shrink: 0;
|
|
||||||
font-size: 0.7rem;
|
|
||||||
}
|
|
||||||
.rcal-pill__label {
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Responsive: stack pill text on small screens */
|
|
||||||
@media (max-width: 640px) {
|
|
||||||
.rcal-cell {
|
|
||||||
min-height: 2.75rem;
|
|
||||||
padding: 0.25rem;
|
|
||||||
}
|
|
||||||
.rcal-pill__label {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.rcal-pill {
|
|
||||||
justify-content: center;
|
|
||||||
padding: 0.125rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>`;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -385,8 +385,8 @@ routes.get("/", (c) => {
|
||||||
modules: getModuleInfoList(),
|
modules: getModuleInfoList(),
|
||||||
theme: "dark",
|
theme: "dark",
|
||||||
body: renderDemo(),
|
body: renderDemo(),
|
||||||
scripts: `<script type="module" src="/modules/rcal/cal-demo.js"></script>`,
|
scripts: `<script type="module" src="/modules/rcal/folk-calendar-view.js?v=3"></script>`,
|
||||||
styles: `<link rel="stylesheet" href="/modules/rcal/cal.css">`,
|
styles: `<link rel="stylesheet" href="/modules/rcal/cal.css?v=2">`,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
return c.html(renderShell({
|
return c.html(renderShell({
|
||||||
|
|
@ -426,4 +426,8 @@ export const calModule: RSpaceModule = {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
acceptsFeeds: ["data", "governance"],
|
acceptsFeeds: ["data", "governance"],
|
||||||
|
outputPaths: [
|
||||||
|
{ path: "saved-views", name: "Saved Views", icon: "👁️", description: "Custom calendar views and filters" },
|
||||||
|
{ path: "events", name: "Events", icon: "📅", description: "Calendar events across all systems" },
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue