@sansavision/create-pulse 0.4.4 → 0.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/dist/index.js +2 -0
  2. package/package.json +2 -2
  3. package/templates/aurora-auth-node-demo/README.md +43 -0
  4. package/templates/aurora-auth-node-demo/aurora.config.ts +15 -0
  5. package/templates/aurora-auth-node-demo/bun.lock +679 -0
  6. package/templates/aurora-auth-node-demo/drizzle.config.ts +9 -0
  7. package/templates/aurora-auth-node-demo/package.json +39 -0
  8. package/templates/aurora-auth-node-demo/postcss.config.mjs +7 -0
  9. package/templates/aurora-auth-node-demo/server.mjs +46 -0
  10. package/templates/aurora-auth-node-demo/src/actions/createMessage.action.server.ts +31 -0
  11. package/templates/aurora-auth-node-demo/src/aurora.auth.ts +65 -0
  12. package/templates/aurora-auth-node-demo/src/lib/auth-client.ts +30 -0
  13. package/templates/aurora-auth-node-demo/src/lib/auth.server.ts +11 -0
  14. package/templates/aurora-auth-node-demo/src/lib/auth.ts +30 -0
  15. package/templates/aurora-auth-node-demo/src/lib/db.ts +6 -0
  16. package/templates/aurora-auth-node-demo/src/lib/pulse.ts +45 -0
  17. package/templates/aurora-auth-node-demo/src/lib/schema.ts +107 -0
  18. package/templates/aurora-auth-node-demo/src/queries/listMessages.server.ts +25 -0
  19. package/templates/aurora-auth-node-demo/src/routes/api/auth/[...slug]/handler.ts +14 -0
  20. package/templates/aurora-auth-node-demo/src/routes/api/pulse/verify/handler.ts +55 -0
  21. package/templates/aurora-auth-node-demo/src/routes/auth/sign-in/page.client.tsx +132 -0
  22. package/templates/aurora-auth-node-demo/src/routes/auth/sign-in/page.tsx +5 -0
  23. package/templates/aurora-auth-node-demo/src/routes/auth/sign-up/page.client.tsx +154 -0
  24. package/templates/aurora-auth-node-demo/src/routes/auth/sign-up/page.tsx +5 -0
  25. package/templates/aurora-auth-node-demo/src/routes/dashboard/demos/arena-game/page.client.tsx +640 -0
  26. package/templates/aurora-auth-node-demo/src/routes/dashboard/demos/arena-game/page.tsx +5 -0
  27. package/templates/aurora-auth-node-demo/src/routes/dashboard/demos/chat/page.client.tsx +349 -0
  28. package/templates/aurora-auth-node-demo/src/routes/dashboard/demos/chat/page.tsx +5 -0
  29. package/templates/aurora-auth-node-demo/src/routes/dashboard/demos/encrypted-chat/page.client.tsx +472 -0
  30. package/templates/aurora-auth-node-demo/src/routes/dashboard/demos/encrypted-chat/page.tsx +5 -0
  31. package/templates/aurora-auth-node-demo/src/routes/dashboard/demos/game-sync/page.client.tsx +375 -0
  32. package/templates/aurora-auth-node-demo/src/routes/dashboard/demos/game-sync/page.tsx +5 -0
  33. package/templates/aurora-auth-node-demo/src/routes/dashboard/demos/queues/page.client.tsx +423 -0
  34. package/templates/aurora-auth-node-demo/src/routes/dashboard/demos/queues/page.tsx +5 -0
  35. package/templates/aurora-auth-node-demo/src/routes/dashboard/demos/video-call/page.client.tsx +840 -0
  36. package/templates/aurora-auth-node-demo/src/routes/dashboard/demos/video-call/page.tsx +5 -0
  37. package/templates/aurora-auth-node-demo/src/routes/dashboard/demos/watch-together/page.client.tsx +722 -0
  38. package/templates/aurora-auth-node-demo/src/routes/dashboard/demos/watch-together/page.tsx +5 -0
  39. package/templates/aurora-auth-node-demo/src/routes/dashboard/layout.client.tsx +113 -0
  40. package/templates/aurora-auth-node-demo/src/routes/dashboard/layout.tsx +5 -0
  41. package/templates/aurora-auth-node-demo/src/routes/dashboard/page.client.tsx +195 -0
  42. package/templates/aurora-auth-node-demo/src/routes/dashboard/page.tsx +5 -0
  43. package/templates/aurora-auth-node-demo/src/routes/favicon.ico +0 -0
  44. package/templates/aurora-auth-node-demo/src/routes/layout.tsx +18 -0
  45. package/templates/aurora-auth-node-demo/src/routes/page.client.tsx +263 -0
  46. package/templates/aurora-auth-node-demo/src/routes/page.tsx +5 -0
  47. package/templates/aurora-auth-node-demo/src/styles/app.css +96 -0
  48. package/templates/aurora-auth-node-demo/tsconfig.json +27 -0
  49. package/templates/aurora-auth-node-demo/tsconfig.tsbuildinfo +1 -0
  50. package/templates/nextjs-auth-demo/next-env.d.ts +1 -1
  51. package/templates/nextjs-auth-demo/package.json +8 -7
  52. package/templates/nextjs-auth-demo/src/app/dashboard/demos/arena-game/page.tsx +20 -3
  53. package/templates/nextjs-auth-demo/src/app/dashboard/demos/chat/page.tsx +108 -23
  54. package/templates/nextjs-auth-demo/src/app/dashboard/demos/encrypted-chat/page.tsx +278 -217
  55. package/templates/nextjs-auth-demo/src/app/dashboard/demos/game-sync/page.tsx +66 -35
  56. package/templates/nextjs-auth-demo/src/app/dashboard/demos/queues/page.tsx +213 -87
  57. package/templates/nextjs-auth-demo/src/app/dashboard/demos/video-call/page.tsx +106 -6
  58. package/templates/nextjs-auth-demo/src/app/dashboard/demos/watch-together/page.tsx +415 -262
  59. package/templates/nextjs-auth-node-demo/.env.example +10 -0
  60. package/templates/nextjs-auth-node-demo/Dockerfile +19 -0
  61. package/templates/nextjs-auth-node-demo/README.md +159 -0
  62. package/templates/nextjs-auth-node-demo/_gitignore +33 -0
  63. package/templates/nextjs-auth-node-demo/drizzle.config.ts +10 -0
  64. package/templates/nextjs-auth-node-demo/eslint.config.mjs +18 -0
  65. package/templates/nextjs-auth-node-demo/next-env.d.ts +6 -0
  66. package/templates/nextjs-auth-node-demo/next.config.ts +7 -0
  67. package/templates/nextjs-auth-node-demo/package.json +38 -0
  68. package/templates/nextjs-auth-node-demo/postcss.config.mjs +7 -0
  69. package/templates/nextjs-auth-node-demo/public/file.svg +1 -0
  70. package/templates/nextjs-auth-node-demo/public/globe.svg +1 -0
  71. package/templates/nextjs-auth-node-demo/public/next.svg +1 -0
  72. package/templates/nextjs-auth-node-demo/public/vercel.svg +1 -0
  73. package/templates/nextjs-auth-node-demo/public/window.svg +1 -0
  74. package/templates/nextjs-auth-node-demo/server.mjs +45 -0
  75. package/templates/nextjs-auth-node-demo/src/app/api/auth/[...all]/route.ts +4 -0
  76. package/templates/nextjs-auth-node-demo/src/app/api/pulse/verify/route.ts +54 -0
  77. package/templates/nextjs-auth-node-demo/src/app/auth/sign-in/page.tsx +131 -0
  78. package/templates/nextjs-auth-node-demo/src/app/auth/sign-up/page.tsx +153 -0
  79. package/templates/nextjs-auth-node-demo/src/app/dashboard/demos/arena-game/page.tsx +640 -0
  80. package/templates/nextjs-auth-node-demo/src/app/dashboard/demos/chat/page.tsx +349 -0
  81. package/templates/nextjs-auth-node-demo/src/app/dashboard/demos/encrypted-chat/page.tsx +472 -0
  82. package/templates/nextjs-auth-node-demo/src/app/dashboard/demos/game-sync/page.tsx +375 -0
  83. package/templates/nextjs-auth-node-demo/src/app/dashboard/demos/queues/page.tsx +423 -0
  84. package/templates/nextjs-auth-node-demo/src/app/dashboard/demos/video-call/page.tsx +840 -0
  85. package/templates/nextjs-auth-node-demo/src/app/dashboard/demos/watch-together/page.tsx +724 -0
  86. package/templates/nextjs-auth-node-demo/src/app/dashboard/layout.tsx +113 -0
  87. package/templates/nextjs-auth-node-demo/src/app/dashboard/page.tsx +195 -0
  88. package/templates/nextjs-auth-node-demo/src/app/favicon.ico +0 -0
  89. package/templates/nextjs-auth-node-demo/src/app/globals.css +96 -0
  90. package/templates/nextjs-auth-node-demo/src/app/layout.tsx +27 -0
  91. package/templates/nextjs-auth-node-demo/src/app/page.tsx +254 -0
  92. package/templates/nextjs-auth-node-demo/src/lib/auth-client.ts +15 -0
  93. package/templates/nextjs-auth-node-demo/src/lib/auth.ts +14 -0
  94. package/templates/nextjs-auth-node-demo/src/lib/db.ts +6 -0
  95. package/templates/nextjs-auth-node-demo/src/lib/pulse.ts +45 -0
  96. package/templates/nextjs-auth-node-demo/src/lib/schema.ts +107 -0
  97. package/templates/nextjs-auth-node-demo/tsconfig.json +34 -0
@@ -0,0 +1,5 @@
1
+ import WatchTogetherPage from "./page.client";
2
+
3
+ export default function Page() {
4
+ return <WatchTogetherPage />;
5
+ }
@@ -0,0 +1,113 @@
1
+
2
+
3
+ import { createClientNavigation } from "@sansavision/aurora/router";
4
+
5
+ import { useSession, signOut } from "@/lib/auth-client";
6
+ import {
7
+ Zap,
8
+ LayoutDashboard,
9
+ MessageSquare,
10
+ PhoneCall,
11
+ Video,
12
+ Database,
13
+ Gamepad2,
14
+ Lock,
15
+ LogOut,
16
+ Loader2,
17
+ ChevronRight,
18
+ Swords,
19
+ } from "lucide-react";
20
+ import { useEffect } from "react";
21
+
22
+ const navItems = [
23
+ { href: "/dashboard", label: "Overview", icon: LayoutDashboard },
24
+ { href: "/dashboard/demos/chat", label: "Real-time Chat", icon: MessageSquare },
25
+ { href: "/dashboard/demos/video-call", label: "Video Call", icon: PhoneCall },
26
+ { href: "/dashboard/demos/watch-together", label: "Watch Together", icon: Video },
27
+ { href: "/dashboard/demos/queues", label: "Durable Queues", icon: Database },
28
+ { href: "/dashboard/demos/game-sync", label: "Game Sync", icon: Gamepad2 },
29
+ { href: "/dashboard/demos/arena-game", label: "Arena Game", icon: Swords },
30
+ { href: "/dashboard/demos/encrypted-chat", label: "E2E Encrypted", icon: Lock },
31
+ ];
32
+
33
+ export default function DashboardLayout({
34
+ children,
35
+ }: {
36
+ children: React.ReactNode;
37
+ }) {
38
+ const { data: session, isPending } = useSession();
39
+ const router = createClientNavigation();
40
+
41
+ useEffect(() => {
42
+ if (!isPending && !session) {
43
+ router.navigate("/auth/sign-in");
44
+ }
45
+ }, [session, isPending, router]);
46
+
47
+ if (isPending) {
48
+ return (
49
+ <div className="min-h-screen flex items-center justify-center">
50
+ <Loader2 className="w-8 h-8 animate-spin text-purple-500" />
51
+ </div>
52
+ );
53
+ }
54
+
55
+ if (!session) return null;
56
+
57
+ return (
58
+ <div className="min-h-screen flex">
59
+ {/* Sidebar */}
60
+ <aside className="w-64 border-r border-slate-800 flex flex-col">
61
+ <div className="p-5 border-b border-slate-800">
62
+ <a href="/" className="flex items-center gap-2">
63
+ <div className="w-8 h-8 rounded-lg bg-gradient-to-br from-purple-500 to-cyan-500 flex items-center justify-center">
64
+ <Zap className="w-5 h-5 text-white" />
65
+ </div>
66
+ <span className="text-lg font-bold">Pulse</span>
67
+ </a>
68
+ </div>
69
+
70
+ <nav className="flex-1 p-3 space-y-1">
71
+ {navItems.map((item) => (
72
+ <a
73
+ key={item.href}
74
+ href={item.href}
75
+ className="flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm text-slate-400 hover:text-white hover:bg-slate-800/50 transition-all group"
76
+ >
77
+ <item.icon className="w-4 h-4" />
78
+ <span className="flex-1">{item.label}</span>
79
+ <ChevronRight className="w-3.5 h-3.5 opacity-0 group-hover:opacity-100 transition-opacity" />
80
+ </a>
81
+ ))}
82
+ </nav>
83
+
84
+ {/* User info */}
85
+ <div className="p-4 border-t border-slate-800">
86
+ <div className="flex items-center gap-3 mb-3">
87
+ <div className="w-8 h-8 rounded-full bg-gradient-to-br from-purple-500 to-cyan-500 flex items-center justify-center text-xs font-bold">
88
+ {session.user.name?.charAt(0)?.toUpperCase() || "?"}
89
+ </div>
90
+ <div className="flex-1 min-w-0">
91
+ <div className="text-sm font-medium truncate">
92
+ {session.user.name}
93
+ </div>
94
+ <div className="text-xs text-slate-500 truncate">
95
+ {session.user.email}
96
+ </div>
97
+ </div>
98
+ </div>
99
+ <button
100
+ onClick={() => signOut().then(() => router.navigate("/"))}
101
+ className="flex items-center gap-2 w-full px-3 py-2 rounded-lg text-sm text-slate-400 hover:text-red-400 hover:bg-red-500/10 transition-all"
102
+ >
103
+ <LogOut className="w-4 h-4" />
104
+ Sign out
105
+ </button>
106
+ </div>
107
+ </aside>
108
+
109
+ {/* Main content */}
110
+ <main className="flex-1 overflow-y-auto">{children}</main>
111
+ </div>
112
+ );
113
+ }
@@ -0,0 +1,5 @@
1
+ import DashboardLayout from "./layout.client";
2
+
3
+ export default function Layout({ children }: { children: React.ReactNode }) {
4
+ return <DashboardLayout>{children}</DashboardLayout>;
5
+ }
@@ -0,0 +1,195 @@
1
+
2
+
3
+ import { useState, useEffect } from "react";
4
+
5
+ import { useSession } from "@/lib/auth-client";
6
+ import { connectWithAuth } from "@/lib/pulse";
7
+ import {
8
+ MessageSquare,
9
+ Video,
10
+ PhoneCall,
11
+ Database,
12
+ Gamepad2,
13
+ Lock,
14
+ ArrowRight,
15
+ Shield,
16
+ Zap,
17
+ Activity,
18
+ Circle,
19
+ Swords,
20
+ } from "lucide-react";
21
+ import type { PulseConnection } from "@sansavision/pulse-sdk";
22
+
23
+ const demos = [
24
+ {
25
+ href: "/dashboard/demos/chat",
26
+ icon: MessageSquare,
27
+ title: "Real-time Chat",
28
+ desc: "Multi-user rooms with presence, typing indicators, and auth identity. Open a second tab with a different user to see live messaging.",
29
+ color: "from-blue-500 to-blue-600",
30
+ badge: "Pub/Sub",
31
+ },
32
+ {
33
+ href: "/dashboard/demos/video-call",
34
+ icon: PhoneCall,
35
+ title: "Video Call",
36
+ desc: "Zoom-like peer-to-peer video calls using Pulse as the signaling layer. Camera, mic, screen sharing, and dynamic grid layout.",
37
+ color: "from-indigo-500 to-sky-500",
38
+ badge: "WebRTC",
39
+ },
40
+ {
41
+ href: "/dashboard/demos/watch-together",
42
+ icon: Video,
43
+ title: "Watch Together",
44
+ desc: "Synchronized video playback. Play, pause, and seek in perfect sync across all connected users.",
45
+ color: "from-pink-500 to-rose-600",
46
+ badge: "Broadcast",
47
+ },
48
+ {
49
+ href: "/dashboard/demos/queues",
50
+ icon: Database,
51
+ title: "Durable Queues",
52
+ desc: "Publish messages, simulate going offline, come back online — messages persist and are delivered. Full ACK/NACK flow.",
53
+ color: "from-amber-500 to-orange-600",
54
+ badge: "Queue",
55
+ },
56
+ {
57
+ href: "/dashboard/demos/game-sync",
58
+ icon: Gamepad2,
59
+ title: "Game State Sync",
60
+ desc: "Shared game board with real-time state sync. Multiple users can interact with the same game state simultaneously.",
61
+ color: "from-green-500 to-emerald-600",
62
+ badge: "State",
63
+ },
64
+ {
65
+ href: "/dashboard/demos/arena-game",
66
+ icon: Swords,
67
+ title: "Arena Game",
68
+ desc: "Real-time multiplayer arena. Create a room, share the code, and compete to collect golden orbs. 30 updates/sec state sync.",
69
+ color: "from-orange-500 to-red-600",
70
+ badge: "Multiplayer",
71
+ },
72
+ {
73
+ href: "/dashboard/demos/encrypted-chat",
74
+ icon: Lock,
75
+ title: "E2E Encrypted Chat",
76
+ desc: "End-to-end encrypted messaging where the relay cannot read content. Key exchange visualization included.",
77
+ color: "from-purple-500 to-violet-600",
78
+ badge: "P2P",
79
+ },
80
+ ];
81
+
82
+ export default function DashboardPage() {
83
+ const { data: session } = useSession();
84
+ const [relayConnected, setRelayConnected] = useState(false);
85
+
86
+ useEffect(() => {
87
+ if (!session) return;
88
+ let conn: PulseConnection | null = null;
89
+ connectWithAuth().then((c) => {
90
+ conn = c;
91
+ setRelayConnected(true);
92
+ c.on("disconnect", () => setRelayConnected(false));
93
+ c.on("reconnected", () => setRelayConnected(true));
94
+ }).catch(() => setRelayConnected(false));
95
+ return () => { conn?.disconnect(); };
96
+ }, [session]);
97
+
98
+ const relayUrl = process.env.NEXT_PUBLIC_PULSE_URL || "ws://localhost:4001";
99
+ const plpUrl = relayUrl.replace(/^ws:\/\//, "plp://").replace(/^wss:\/\//, "plps://");
100
+
101
+ return (
102
+ <div className="p-8">
103
+ {/* Header */}
104
+ <div className="mb-8">
105
+ <h1 className="text-3xl font-bold mb-2">
106
+ Welcome, {session?.user?.name?.split(" ")[0] || "there"}
107
+ </h1>
108
+ <p className="text-slate-400 text-lg">
109
+ Choose a demo to explore Pulse&apos;s capabilities with authenticated
110
+ connections.
111
+ </p>
112
+ </div>
113
+
114
+ {/* Connection status */}
115
+ <div className="glass rounded-xl p-5 mb-8 flex items-center gap-6 flex-wrap">
116
+ <div className="flex items-center gap-2">
117
+ {relayConnected ? (
118
+ <Circle className="w-3 h-3 text-green-400 fill-green-400 animate-pulse" />
119
+ ) : (
120
+ <Circle className="w-3 h-3 text-red-400 fill-red-400" />
121
+ )}
122
+ <span className={`text-sm font-medium ${relayConnected ? 'text-green-400' : 'text-red-400'}`}>
123
+ {relayConnected ? 'Connected' : 'Disconnected'}
124
+ </span>
125
+ </div>
126
+ <div className="flex items-center gap-2">
127
+ <Shield className="w-4 h-4 text-green-400" />
128
+ <span className="text-sm text-green-400 font-medium">
129
+ Authenticated
130
+ </span>
131
+ </div>
132
+ <div className="flex items-center gap-2">
133
+ <Zap className="w-4 h-4 text-cyan-400" />
134
+ <span className="text-sm text-slate-400">
135
+ Relay:{" "}
136
+ <code className="text-cyan-400 bg-cyan-500/10 px-1.5 py-0.5 rounded text-xs">
137
+ {plpUrl}
138
+ </code>
139
+ </span>
140
+ </div>
141
+ <div className="flex items-center gap-2">
142
+ <Activity className="w-4 h-4 text-purple-400" />
143
+ <span className="text-sm text-slate-400">
144
+ Auth Mode:{" "}
145
+ <code className="text-purple-400 bg-purple-500/10 px-1.5 py-0.5 rounded text-xs">
146
+ webhook
147
+ </code>
148
+ </span>
149
+ </div>
150
+ </div>
151
+
152
+ {/* Tip */}
153
+ <div className="glass rounded-xl p-5 mb-8 border-l-4 border-purple-500">
154
+ <h3 className="text-sm font-semibold text-purple-400 mb-1">
155
+ 💡 Multi-User Tip
156
+ </h3>
157
+ <p className="text-sm text-slate-400">
158
+ Open a second browser tab (or incognito window), create a different
159
+ account, and interact simultaneously. Watch messages, video sync, and
160
+ game state flow in real-time between users.
161
+ </p>
162
+ </div>
163
+
164
+ {/* Demo cards */}
165
+ <div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
166
+ {demos.map((demo) => (
167
+ <a key={demo.href} href={demo.href}>
168
+ <div className="demo-card glass rounded-2xl p-6 h-full cursor-pointer group">
169
+ <div className="flex items-start justify-between mb-4">
170
+ <div
171
+ className={`w-12 h-12 rounded-xl bg-gradient-to-br ${demo.color} flex items-center justify-center`}
172
+ >
173
+ <demo.icon className="w-6 h-6 text-white" />
174
+ </div>
175
+ <span className="text-xs font-mono px-2 py-1 rounded-md bg-slate-800 text-slate-400 border border-slate-700">
176
+ {demo.badge}
177
+ </span>
178
+ </div>
179
+ <h3 className="text-lg font-semibold mb-2 group-hover:text-purple-400 transition-colors">
180
+ {demo.title}
181
+ </h3>
182
+ <p className="text-sm text-slate-400 leading-relaxed mb-4">
183
+ {demo.desc}
184
+ </p>
185
+ <div className="flex items-center gap-1 text-sm text-purple-400 font-medium">
186
+ Open Demo
187
+ <ArrowRight className="w-3.5 h-3.5 group-hover:translate-x-1 transition-transform" />
188
+ </div>
189
+ </div>
190
+ </a>
191
+ ))}
192
+ </div>
193
+ </div>
194
+ );
195
+ }
@@ -0,0 +1,5 @@
1
+ import DashboardPage from "./page.client";
2
+
3
+ export default function Page() {
4
+ return <DashboardPage />;
5
+ }
@@ -0,0 +1,18 @@
1
+ // Styles are auto-loaded by Aurora from src/styles/app.css
2
+ export default function RootLayout({
3
+ children,
4
+ }: {
5
+ children: React.ReactNode;
6
+ }) {
7
+ return (
8
+ <html lang="en" className="dark">
9
+ <head>
10
+ <link
11
+ href="https://fonts.googleapis.com/css2?family=Inter:wght,300;400;500;600;700;800;900&display=swap"
12
+ rel="stylesheet"
13
+ />
14
+ </head>
15
+ <body className="min-h-screen antialiased">{children}</body>
16
+ </html>
17
+ );
18
+ }
@@ -0,0 +1,263 @@
1
+
2
+
3
+
4
+ import {
5
+ Zap,
6
+ Shield,
7
+ Wifi,
8
+ MessageSquare,
9
+ Video,
10
+ Database,
11
+ Gamepad2,
12
+ Lock,
13
+ ArrowRight,
14
+ Globe,
15
+ Clock,
16
+ Activity,
17
+ } from "lucide-react";
18
+
19
+ export default function LandingPage() {
20
+ return (
21
+ <div className="min-h-screen">
22
+ {/* Nav */}
23
+ <nav className="fixed top-0 left-0 right-0 z-50 glass">
24
+ <div className="max-w-7xl mx-auto px-6 py-4 flex items-center justify-between">
25
+ <div className="flex items-center gap-2">
26
+ <div className="w-8 h-8 rounded-lg bg-gradient-to-br from-purple-500 to-cyan-500 flex items-center justify-center">
27
+ <Zap className="w-5 h-5 text-white" />
28
+ </div>
29
+ <span className="text-xl font-bold">Pulse</span>
30
+ </div>
31
+ <div className="flex items-center gap-4">
32
+ <a
33
+ href="/auth/sign-in"
34
+ className="px-4 py-2 text-sm text-slate-300 hover:text-white transition-colors"
35
+ >
36
+ Sign In
37
+ </a>
38
+ <a
39
+ href="/auth/sign-up"
40
+ className="px-5 py-2 text-sm bg-purple-600 hover:bg-purple-500 rounded-lg font-medium transition-all hover:shadow-lg hover:shadow-purple-500/25"
41
+ >
42
+ Get Started
43
+ </a>
44
+ </div>
45
+ </div>
46
+ </nav>
47
+
48
+ {/* Hero */}
49
+ <section className="relative pt-32 pb-20 px-6 overflow-hidden">
50
+ {/* Background gradient orbs */}
51
+ <div className="absolute top-20 left-1/4 w-96 h-96 bg-purple-500/20 rounded-full blur-[128px]" />
52
+ <div className="absolute top-40 right-1/4 w-96 h-96 bg-cyan-500/20 rounded-full blur-[128px]" />
53
+
54
+ <div className="max-w-5xl mx-auto text-center relative z-10">
55
+ <div className="inline-flex items-center gap-2 px-4 py-1.5 rounded-full border border-purple-500/30 bg-purple-500/10 text-purple-300 text-sm mb-8">
56
+ <Activity className="w-3.5 h-3.5" />
57
+ <span>PLP — Pulse Line Protocol v1</span>
58
+ </div>
59
+
60
+ <h1 className="text-5xl md:text-7xl font-extrabold tracking-tight leading-tight mb-6">
61
+ <span className="gradient-text">The Real-Time Protocol</span>
62
+ <br />
63
+ <span className="text-slate-200">for Modern Applications</span>
64
+ </h1>
65
+
66
+ <p className="text-xl text-slate-400 max-w-2xl mx-auto mb-10 leading-relaxed">
67
+ Sub-millisecond latency, end-to-end encryption, durable queues, and
68
+ external auth — all in one binary. Built with Rust, powered by QUIC.
69
+ </p>
70
+
71
+ <div className="flex items-center justify-center gap-4">
72
+ <a
73
+ href="/auth/sign-up"
74
+ className="inline-flex items-center gap-2 px-8 py-3.5 bg-gradient-to-r from-purple-600 to-purple-500 hover:from-purple-500 hover:to-purple-400 rounded-xl font-semibold text-lg transition-all hover:shadow-xl hover:shadow-purple-500/25 pulse-glow"
75
+ >
76
+ Try the Demo
77
+ <ArrowRight className="w-5 h-5" />
78
+ </a>
79
+ <a
80
+ href="https://github.com/Sansa-Organisation/pulse"
81
+ target="_blank"
82
+ rel="noopener"
83
+ className="px-8 py-3.5 rounded-xl font-semibold text-lg border border-slate-700 hover:border-slate-500 hover:bg-slate-800/50 transition-all"
84
+ >
85
+ GitHub
86
+ </a>
87
+ </div>
88
+
89
+ {/* Stats */}
90
+ <div className="grid grid-cols-3 gap-8 max-w-xl mx-auto mt-16">
91
+ {[
92
+ { label: "Latency", value: "<1ms", icon: Clock },
93
+ { label: "Connections", value: "100K+", icon: Globe },
94
+ { label: "Uptime", value: "99.99%", icon: Activity },
95
+ ].map((stat) => (
96
+ <div key={stat.label} className="text-center">
97
+ {(() => {
98
+ const Icon = stat.icon;
99
+ return <Icon className="w-5 h-5 text-purple-400 mx-auto mb-2" />;
100
+ })()}
101
+ <div className="text-2xl font-bold">{stat.value}</div>
102
+ <div className="text-sm text-slate-500">{stat.label}</div>
103
+ </div>
104
+ ))}
105
+ </div>
106
+ </div>
107
+ </section>
108
+
109
+ {/* Features */}
110
+ <section className="py-20 px-6">
111
+ <div className="max-w-6xl mx-auto">
112
+ <div className="text-center mb-16">
113
+ <h2 className="text-3xl md:text-4xl font-bold mb-4">
114
+ Everything you need, out of the box
115
+ </h2>
116
+ <p className="text-slate-400 text-lg max-w-xl mx-auto">
117
+ One protocol, five powerful modes. Each demo below is fully functional
118
+ and uses real PLP connections.
119
+ </p>
120
+ </div>
121
+
122
+ <div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
123
+ {[
124
+ {
125
+ icon: MessageSquare,
126
+ title: "Real-time Chat",
127
+ desc: "Multi-user rooms with typing indicators, presence, and message history.",
128
+ color: "from-blue-500 to-blue-600",
129
+ },
130
+ {
131
+ icon: Video,
132
+ title: "Watch Together",
133
+ desc: "Synchronized video playback — play, pause, seek in perfect sync.",
134
+ color: "from-pink-500 to-rose-600",
135
+ },
136
+ {
137
+ icon: Database,
138
+ title: "Durable Queues",
139
+ desc: "Persist messages, simulate offline, come back online — nothing lost.",
140
+ color: "from-amber-500 to-orange-600",
141
+ },
142
+ {
143
+ icon: Gamepad2,
144
+ title: "Game State Sync",
145
+ desc: "Real-time shared state with conflict-free resolution.",
146
+ color: "from-green-500 to-emerald-600",
147
+ },
148
+ {
149
+ icon: Lock,
150
+ title: "E2E Encrypted Chat",
151
+ desc: "End-to-end encryption with relay-blind transport.",
152
+ color: "from-purple-500 to-violet-600",
153
+ },
154
+ {
155
+ icon: Shield,
156
+ title: "Auth Context",
157
+ desc: "External auth integration — Better Auth, Clerk, Auth0, and more.",
158
+ color: "from-cyan-500 to-teal-600",
159
+ },
160
+ ].map((feature) => (
161
+ <div
162
+ key={feature.title}
163
+ className="demo-card glass rounded-2xl p-6"
164
+ >
165
+ <div
166
+ className={`w-12 h-12 rounded-xl bg-gradient-to-br ${feature.color} flex items-center justify-center mb-4`}
167
+ >
168
+ {(() => {
169
+ const Icon = feature.icon;
170
+ return <Icon className="w-6 h-6 text-white" />;
171
+ })()}
172
+ </div>
173
+ <h3 className="text-lg font-semibold mb-2">{feature.title}</h3>
174
+ <p className="text-slate-400 text-sm leading-relaxed">
175
+ {feature.desc}
176
+ </p>
177
+ </div>
178
+ ))}
179
+ </div>
180
+ </div>
181
+ </section>
182
+
183
+ {/* Architecture */}
184
+ <section className="py-20 px-6">
185
+ <div className="max-w-4xl mx-auto">
186
+ <div className="glass rounded-2xl p-8 md:p-12">
187
+ <h2 className="text-2xl font-bold mb-6 text-center">
188
+ How Auth Works
189
+ </h2>
190
+ <div className="flex flex-col md:flex-row items-center gap-6">
191
+ {[
192
+ {
193
+ step: 1,
194
+ icon: Shield,
195
+ title: "Sign In",
196
+ desc: "User authenticates via Better Auth (email/password)",
197
+ },
198
+ {
199
+ step: 2,
200
+ icon: Wifi,
201
+ title: "Connect",
202
+ desc: "SDK sends bearer token in PLP CONNECT handshake",
203
+ },
204
+ {
205
+ step: 3,
206
+ icon: Zap,
207
+ title: "Verify",
208
+ desc: "Relay calls webhook → Better Auth validates session",
209
+ },
210
+ ].map((item, i) => (
211
+ <div key={item.step} className="flex-1 text-center">
212
+ <div className="w-10 h-10 rounded-full bg-purple-600 text-white font-bold flex items-center justify-center mx-auto mb-3">
213
+ {item.step}
214
+ </div>
215
+ {(() => {
216
+ const Icon = item.icon;
217
+ return <Icon className="w-6 h-6 text-purple-400 mx-auto mb-2" />;
218
+ })()}
219
+ <h4 className="font-semibold mb-1">{item.title}</h4>
220
+ <p className="text-sm text-slate-400">{item.desc}</p>
221
+ {i < 2 && (
222
+ <ArrowRight className="w-5 h-5 text-slate-600 mx-auto mt-4 hidden md:block rotate-0" />
223
+ )}
224
+ </div>
225
+ ))}
226
+ </div>
227
+ </div>
228
+ </div>
229
+ </section>
230
+
231
+ {/* CTA */}
232
+ <section className="py-20 px-6">
233
+ <div className="max-w-3xl mx-auto text-center">
234
+ <h2 className="text-3xl md:text-4xl font-bold mb-4">
235
+ Ready to experience it?
236
+ </h2>
237
+ <p className="text-slate-400 text-lg mb-8">
238
+ Create an account and explore every feature live. Open two browser
239
+ tabs to see multi-user sync in action.
240
+ </p>
241
+ <a
242
+ href="/auth/sign-up"
243
+ className="inline-flex items-center gap-2 px-10 py-4 bg-gradient-to-r from-purple-600 to-cyan-600 hover:from-purple-500 hover:to-cyan-500 rounded-xl font-semibold text-lg transition-all hover:shadow-xl hover:shadow-purple-500/25"
244
+ >
245
+ Create Account
246
+ <ArrowRight className="w-5 h-5" />
247
+ </a>
248
+ </div>
249
+ </section>
250
+
251
+ {/* Footer */}
252
+ <footer className="border-t border-slate-800 py-8 px-6">
253
+ <div className="max-w-6xl mx-auto flex items-center justify-between text-sm text-slate-500">
254
+ <span>© 2026 Sansa Vision. Pulse Protocol.</span>
255
+ <div className="flex items-center gap-1">
256
+ <div className="status-online" />
257
+ <span>Relay Live</span>
258
+ </div>
259
+ </div>
260
+ </footer>
261
+ </div>
262
+ );
263
+ }
@@ -0,0 +1,5 @@
1
+ import LandingPage from "./page.client";
2
+
3
+ export default function Page() {
4
+ return <LandingPage />;
5
+ }