From 0c2e198f2609d8b6d50d4027160956af1f6a998a Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Wed, 18 Feb 2026 09:24:37 +0000 Subject: [PATCH] feat: add landing page, move calendar app to /calendar route Create marketing landing page at / with hero, features (spatial, zoom, lunar), how-it-works, ecosystem section, and r-suite footer. Move full calendar app to /calendar. Add rcal.online domain to Traefik labels. Co-Authored-By: Claude Opus 4.6 --- docker-compose.yml | 2 +- src/app/calendar/page.tsx | 170 +++++++++++++++++++ src/app/page.tsx | 342 ++++++++++++++++++++------------------ 3 files changed, 354 insertions(+), 160 deletions(-) create mode 100644 src/app/calendar/page.tsx diff --git a/docker-compose.yml b/docker-compose.yml index 6badcef..cc75954 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -18,7 +18,7 @@ services: - /tmp labels: - "traefik.enable=true" - - "traefik.http.routers.rcal.rule=Host(`rcal.jeffemmett.com`)" + - "traefik.http.routers.rcal.rule=Host(`rcal.jeffemmett.com`) || Host(`rcal.online`) || Host(`www.rcal.online`)" - "traefik.http.routers.rcal.entrypoints=web" - "traefik.http.services.rcal.loadbalancer.server.port=3000" networks: diff --git a/src/app/calendar/page.tsx b/src/app/calendar/page.tsx new file mode 100644 index 0000000..183e225 --- /dev/null +++ b/src/app/calendar/page.tsx @@ -0,0 +1,170 @@ +'use client' + +import { useState, useEffect, useCallback } from 'react' +import { Calendar as CalendarIcon, MapPin, Clock, ZoomIn, ZoomOut, Link2, Unlink2 } from 'lucide-react' +import { TemporalZoomController } from '@/components/calendar' +import { CalendarHeader } from '@/components/calendar/CalendarHeader' +import { CalendarSidebar } from '@/components/calendar/CalendarSidebar' +import { TabLayout } from '@/components/ui/TabLayout' +import { TemporalTab } from '@/components/tabs/TemporalTab' +import { SpatialTab } from '@/components/tabs/SpatialTab' +import { LunarTab } from '@/components/tabs/LunarTab' +import { ContextTab } from '@/components/tabs/ContextTab' +import { useCalendarStore, useEffectiveSpatialGranularity } from '@/lib/store' +import { TemporalGranularity, TEMPORAL_GRANULARITY_LABELS, GRANULARITY_LABELS } from '@/lib/types' +import type { TabView } from '@/lib/types' + +export default function Home() { + const [sidebarOpen, setSidebarOpen] = useState(true) + const [zoomPanelOpen, setZoomPanelOpen] = useState(false) + const { + temporalGranularity, + activeTab, + setActiveTab, + zoomCoupled, + toggleZoomCoupled, + zoomIn, + zoomOut, + } = useCalendarStore() + const effectiveSpatial = useEffectiveSpatialGranularity() + + // Keyboard shortcuts + const handleKeyDown = useCallback( + (e: KeyboardEvent) => { + if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) return + if (e.ctrlKey || e.metaKey || e.altKey) return + + switch (e.key) { + case 'l': + case 'L': + e.preventDefault() + toggleZoomCoupled() + break + // Tab switching: 1-4 + case '1': + e.preventDefault() + setActiveTab('temporal') + break + case '2': + e.preventDefault() + setActiveTab('spatial') + break + case '3': + e.preventDefault() + setActiveTab('lunar') + break + case '4': + e.preventDefault() + setActiveTab('context') + break + } + }, + [toggleZoomCoupled, setActiveTab] + ) + + useEffect(() => { + window.addEventListener('keydown', handleKeyDown) + return () => window.removeEventListener('keydown', handleKeyDown) + }, [handleKeyDown]) + + return ( +
+ {/* Sidebar */} + {sidebarOpen && ( + setSidebarOpen(false)} /> + )} + + {/* Main content */} +
+ setSidebarOpen(!sidebarOpen)} + sidebarOpen={sidebarOpen} + /> + + {/* Main area with optional zoom panel */} +
+ {/* Tab layout */} +
+ + {{ + temporal: , + spatial: , + lunar: , + context: , + }} + +
+ + {/* Zoom control panel (collapsible) */} + {zoomPanelOpen && ( + + )} +
+ + {/* Footer with calendar info and quick zoom controls */} +
+
+
+ + + Gregorian + + + + {TEMPORAL_GRANULARITY_LABELS[temporalGranularity]} + + + + {GRANULARITY_LABELS[effectiveSpatial]} + + {activeTab === 'spatial' && ( + + )} +
+ + {/* Quick controls */} +
+ + + +
+
+
+
+
+ ) +} diff --git a/src/app/page.tsx b/src/app/page.tsx index 183e225..3bc172c 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,170 +1,194 @@ -'use client' - -import { useState, useEffect, useCallback } from 'react' -import { Calendar as CalendarIcon, MapPin, Clock, ZoomIn, ZoomOut, Link2, Unlink2 } from 'lucide-react' -import { TemporalZoomController } from '@/components/calendar' -import { CalendarHeader } from '@/components/calendar/CalendarHeader' -import { CalendarSidebar } from '@/components/calendar/CalendarSidebar' -import { TabLayout } from '@/components/ui/TabLayout' -import { TemporalTab } from '@/components/tabs/TemporalTab' -import { SpatialTab } from '@/components/tabs/SpatialTab' -import { LunarTab } from '@/components/tabs/LunarTab' -import { ContextTab } from '@/components/tabs/ContextTab' -import { useCalendarStore, useEffectiveSpatialGranularity } from '@/lib/store' -import { TemporalGranularity, TEMPORAL_GRANULARITY_LABELS, GRANULARITY_LABELS } from '@/lib/types' -import type { TabView } from '@/lib/types' - -export default function Home() { - const [sidebarOpen, setSidebarOpen] = useState(true) - const [zoomPanelOpen, setZoomPanelOpen] = useState(false) - const { - temporalGranularity, - activeTab, - setActiveTab, - zoomCoupled, - toggleZoomCoupled, - zoomIn, - zoomOut, - } = useCalendarStore() - const effectiveSpatial = useEffectiveSpatialGranularity() - - // Keyboard shortcuts - const handleKeyDown = useCallback( - (e: KeyboardEvent) => { - if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) return - if (e.ctrlKey || e.metaKey || e.altKey) return - - switch (e.key) { - case 'l': - case 'L': - e.preventDefault() - toggleZoomCoupled() - break - // Tab switching: 1-4 - case '1': - e.preventDefault() - setActiveTab('temporal') - break - case '2': - e.preventDefault() - setActiveTab('spatial') - break - case '3': - e.preventDefault() - setActiveTab('lunar') - break - case '4': - e.preventDefault() - setActiveTab('context') - break - } - }, - [toggleZoomCoupled, setActiveTab] - ) - - useEffect(() => { - window.addEventListener('keydown', handleKeyDown) - return () => window.removeEventListener('keydown', handleKeyDown) - }, [handleKeyDown]) +import Link from 'next/link' +export default function LandingPage() { return ( -
- {/* Sidebar */} - {sidebarOpen && ( - setSidebarOpen(false)} /> - )} - - {/* Main content */} -
- setSidebarOpen(!sidebarOpen)} - sidebarOpen={sidebarOpen} - /> - - {/* Main area with optional zoom panel */} -
- {/* Tab layout */} -
- - {{ - temporal: , - spatial: , - lunar: , - context: , - }} - +
+ {/* Nav */} + - {/* Footer with calendar info and quick zoom controls */} -
-
-
- - - Gregorian - - - - {TEMPORAL_GRANULARITY_LABELS[temporalGranularity]} - - - - {GRANULARITY_LABELS[effectiveSpatial]} - - {activeTab === 'spatial' && ( - - )} + {/* Hero */} +
+
+

+ Group Calendars,{' '} + + Simplified + +

+

+ One calendar your whole group can see. No more back-and-forth — just shared context for when and where to meet. +

+
+ + Try the Demo + + + Learn More + +
+
+
+ + {/* Features */} +
+
+

+ A calendar that thinks in space and time +

+
+
+
+ 🗺 +
+

Where + When, Together

+

+ See events on a calendar and a map side by side. Plan meetups knowing where everyone is, not just when. +

- - {/* Quick controls */} -
- - - +
+
+ 🔭 +
+

Zoom From Hours to Eras

+

+ Ten levels of time. See today's meetings, zoom out to the whole season, or plan years ahead — all in one view. +

+
+
+
+ 🌙 +
+

Moon & Natural Cycles

+

+ Built-in lunar phase overlay with eclipse detection. Plan around full moons, new moons, and solstices. +

-
-
+
+ + + {/* How It Works */} +
+
+

How It Works

+
+
+
+ 1 +
+
+

Add your events

+

+ Create events with a time and a place. Or import from an existing calendar source. +

+
+
+
+
+ 2 +
+
+

Share with your group

+

+ Everyone sees the same calendar. Same context, same view. +

+
+
+
+
+ 3 +
+
+

Find the right zoom level

+

+ From a single hour to a decade. The calendar adapts to the scale you need. +

+
+
+
+
+
+ + {/* Ecosystem */} +
+
+
+

Works with the r* ecosystem

+

+ rCal embeds into rTrips, rMaps, rNetwork, and more. Any r-tool can display a calendar view through the context API. +

+ + Open rCal + +
+
+
+ + {/* Footer */} +
) }