lytx 0.3.0

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 (213) hide show
  1. package/.env.example +37 -0
  2. package/README.md +486 -0
  3. package/alchemy.run.ts +155 -0
  4. package/cli/bootstrap-admin.ts +284 -0
  5. package/cli/deploy-staging.ts +692 -0
  6. package/cli/import-events.ts +628 -0
  7. package/cli/import-sites.ts +518 -0
  8. package/cli/index.ts +609 -0
  9. package/cli/init-db.ts +269 -0
  10. package/cli/migrate-to-durable-objects.ts +564 -0
  11. package/cli/migration-worker.ts +300 -0
  12. package/cli/performance-test.ts +588 -0
  13. package/cli/pg/client.ts +4 -0
  14. package/cli/pg/new-site.ts +153 -0
  15. package/cli/rollback-durable-objects.ts +622 -0
  16. package/cli/seed-data.ts +459 -0
  17. package/cli/setup.js +18 -0
  18. package/cli/setup.ts +463 -0
  19. package/cli/validate-migration.ts +200 -0
  20. package/cli/wrangler-migration.jsonc +28 -0
  21. package/db/adapter.ts +166 -0
  22. package/db/analytics_engine/client.ts +0 -0
  23. package/db/analytics_engine/sites.ts +0 -0
  24. package/db/client.ts +16 -0
  25. package/db/d1/client.ts +8 -0
  26. package/db/d1/drizzle.config.ts +35 -0
  27. package/db/d1/migrations/0000_true_maelstrom.sql +165 -0
  28. package/db/d1/migrations/0001_wonderful_bloodaxe.sql +12 -0
  29. package/db/d1/migrations/0002_late_frightful_four.sql +1 -0
  30. package/db/d1/migrations/0003_cuddly_obadiah_stane.sql +16 -0
  31. package/db/d1/migrations/0004_mute_stardust.sql +1 -0
  32. package/db/d1/migrations/0005_awesome_silvermane.sql +3 -0
  33. package/db/d1/migrations/0006_volatile_shriek.sql +2 -0
  34. package/db/d1/migrations/0007_superb_lila_cheney.sql +1 -0
  35. package/db/d1/migrations/0008_bitter_longshot.sql +17 -0
  36. package/db/d1/migrations/0009_wonderful_madame_masque.sql +28 -0
  37. package/db/d1/migrations/meta/0000_snapshot.json +1112 -0
  38. package/db/d1/migrations/meta/0001_snapshot.json +1187 -0
  39. package/db/d1/migrations/meta/0002_snapshot.json +1194 -0
  40. package/db/d1/migrations/meta/0003_snapshot.json +1296 -0
  41. package/db/d1/migrations/meta/0004_snapshot.json +1303 -0
  42. package/db/d1/migrations/meta/0005_snapshot.json +1325 -0
  43. package/db/d1/migrations/meta/0006_snapshot.json +1339 -0
  44. package/db/d1/migrations/meta/0007_snapshot.json +1347 -0
  45. package/db/d1/migrations/meta/0008_snapshot.json +1464 -0
  46. package/db/d1/migrations/meta/0009_snapshot.json +1648 -0
  47. package/db/d1/migrations/meta/_journal.json +76 -0
  48. package/db/d1/schema.ts +407 -0
  49. package/db/d1/sites.ts +374 -0
  50. package/db/d1/teamAiUsage.ts +101 -0
  51. package/db/d1/teams.ts +127 -0
  52. package/db/durable/drizzle.config.ts +8 -0
  53. package/db/durable/durableObjectClient.ts +480 -0
  54. package/db/durable/events.ts +100 -0
  55. package/db/durable/migrations/0000_fair_bucky.sql +38 -0
  56. package/db/durable/migrations/meta/0000_snapshot.json +278 -0
  57. package/db/durable/migrations/meta/_journal.json +13 -0
  58. package/db/durable/migrations/migrations.js +10 -0
  59. package/db/durable/schema.ts +5 -0
  60. package/db/durable/siteDurableObject.ts +1352 -0
  61. package/db/durable/types.ts +53 -0
  62. package/db/postgres/client.ts +13 -0
  63. package/db/postgres/drizzle.config.ts +12 -0
  64. package/db/postgres/migrations/0000_brainy_sprite.sql +116 -0
  65. package/db/postgres/migrations/meta/0000_snapshot.json +681 -0
  66. package/db/postgres/migrations/meta/_journal.json +13 -0
  67. package/db/postgres/schema.ts +145 -0
  68. package/db/postgres/sites.ts +118 -0
  69. package/db/tranformReports.ts +595 -0
  70. package/db/types.ts +55 -0
  71. package/endpoints/api_worker.tsx +1854 -0
  72. package/endpoints/site_do_worker.ts +11 -0
  73. package/index.d.ts +63 -0
  74. package/index.ts +83 -0
  75. package/lib/auth.ts +279 -0
  76. package/lib/geojson/world_countries.json +45307 -0
  77. package/lib/random_name.ts +41 -0
  78. package/lib/sendMail.ts +252 -0
  79. package/package.json +142 -0
  80. package/public/favicon.ico +0 -0
  81. package/public/images/android-chrome-192x192.png +0 -0
  82. package/public/images/android-chrome-512x512.png +0 -0
  83. package/public/images/apple-touch-icon.png +0 -0
  84. package/public/images/favicon-16x16.png +0 -0
  85. package/public/images/favicon-32x32.png +0 -0
  86. package/public/images/lytx_dark_dashboard.png +0 -0
  87. package/public/images/lytx_light_dashboard.png +0 -0
  88. package/public/images/safari-pinned-tab.svg +4 -0
  89. package/public/logo.png +0 -0
  90. package/public/site.webmanifest +26 -0
  91. package/public/sw.js +107 -0
  92. package/src/Document.tsx +86 -0
  93. package/src/api/ai_api.ts +1156 -0
  94. package/src/api/authMiddleware.ts +45 -0
  95. package/src/api/auth_api.ts +465 -0
  96. package/src/api/event_labels_api.ts +193 -0
  97. package/src/api/events_api.ts +210 -0
  98. package/src/api/queueWorker.ts +303 -0
  99. package/src/api/reports_api.ts +278 -0
  100. package/src/api/seed_api.ts +288 -0
  101. package/src/api/sites_api.ts +904 -0
  102. package/src/api/tag_api.ts +458 -0
  103. package/src/api/tag_api_v2.ts +289 -0
  104. package/src/api/team_api.ts +456 -0
  105. package/src/app/Dashboard.tsx +1339 -0
  106. package/src/app/Events.tsx +974 -0
  107. package/src/app/Explore.tsx +312 -0
  108. package/src/app/Layout.tsx +58 -0
  109. package/src/app/Settings.tsx +1302 -0
  110. package/src/app/components/DashboardCard.tsx +118 -0
  111. package/src/app/components/EditableCell.tsx +123 -0
  112. package/src/app/components/EventForm.tsx +93 -0
  113. package/src/app/components/MarketingFooter.tsx +49 -0
  114. package/src/app/components/MarketingNav.tsx +150 -0
  115. package/src/app/components/Nav.tsx +755 -0
  116. package/src/app/components/NewSiteSetup.tsx +298 -0
  117. package/src/app/components/SQLEditor.tsx +740 -0
  118. package/src/app/components/SiteSelector.tsx +126 -0
  119. package/src/app/components/SiteTag.tsx +42 -0
  120. package/src/app/components/SiteTagInstallCard.tsx +241 -0
  121. package/src/app/components/WorldMapCard.tsx +337 -0
  122. package/src/app/components/charts/ChartComponents.tsx +1481 -0
  123. package/src/app/components/charts/EventFunnel.tsx +45 -0
  124. package/src/app/components/charts/EventSummary.tsx +194 -0
  125. package/src/app/components/charts/SankeyFlows.tsx +72 -0
  126. package/src/app/components/marketing/CheckIcon.tsx +16 -0
  127. package/src/app/components/marketing/MarketingLayout.tsx +23 -0
  128. package/src/app/components/marketing/SectionHeading.tsx +35 -0
  129. package/src/app/components/reports/AskAiWorkspace.tsx +371 -0
  130. package/src/app/components/reports/CreateReportStarter.tsx +74 -0
  131. package/src/app/components/reports/DashboardRouteFiltersContext.tsx +14 -0
  132. package/src/app/components/reports/DashboardToolbar.tsx +154 -0
  133. package/src/app/components/reports/DashboardWorkspaceLayout.tsx +63 -0
  134. package/src/app/components/reports/DashboardWorkspaceShell.tsx +118 -0
  135. package/src/app/components/reports/ReportBuilderWorkspace.tsx +76 -0
  136. package/src/app/components/reports/custom/CustomReportBuilderPage.tsx +1667 -0
  137. package/src/app/components/reports/custom/ReportWidgetChart.tsx +297 -0
  138. package/src/app/components/reports/custom/buildWidgetSql.ts +151 -0
  139. package/src/app/components/reports/custom/chartPalettes.ts +18 -0
  140. package/src/app/components/reports/custom/types.ts +50 -0
  141. package/src/app/components/reports/reportBuilderMenuItems.ts +17 -0
  142. package/src/app/components/reports/useDashboardToolbarControls.tsx +235 -0
  143. package/src/app/components/ui/AlertBanner.tsx +101 -0
  144. package/src/app/components/ui/Button.tsx +55 -0
  145. package/src/app/components/ui/Card.tsx +80 -0
  146. package/src/app/components/ui/Input.tsx +72 -0
  147. package/src/app/components/ui/Link.tsx +23 -0
  148. package/src/app/components/ui/ReportBuilderMenu.tsx +246 -0
  149. package/src/app/components/ui/ThemeToggle.tsx +54 -0
  150. package/src/app/constants.ts +6 -0
  151. package/src/app/headers.ts +33 -0
  152. package/src/app/providers/AuthProvider.tsx +189 -0
  153. package/src/app/providers/ClientProviders.tsx +18 -0
  154. package/src/app/providers/QueryProvider.tsx +23 -0
  155. package/src/app/providers/ThemeProvider.tsx +88 -0
  156. package/src/app/utils/chartThemes.ts +146 -0
  157. package/src/app/utils/keybinds.ts +96 -0
  158. package/src/app/utils/media.tsx +24 -0
  159. package/src/client.tsx +114 -0
  160. package/src/config/createLytxAppConfig.ts +252 -0
  161. package/src/config/resourceNames.ts +88 -0
  162. package/src/db/index.ts +67 -0
  163. package/src/index.css +285 -0
  164. package/src/lib/featureFlags.ts +69 -0
  165. package/src/pages/GetStarted.tsx +290 -0
  166. package/src/pages/Home.tsx +268 -0
  167. package/src/pages/Login.tsx +283 -0
  168. package/src/pages/PrivacyPolicy.tsx +120 -0
  169. package/src/pages/Signup.tsx +267 -0
  170. package/src/pages/TermsOfService.tsx +126 -0
  171. package/src/pages/VerifyEmail.tsx +56 -0
  172. package/src/session/durableObject.ts +7 -0
  173. package/src/session/siteSchema.ts +86 -0
  174. package/src/session/types.ts +36 -0
  175. package/src/templates/README.md +80 -0
  176. package/src/templates/cleanFunctions.js +44 -0
  177. package/src/templates/embedFunctions.js +52 -0
  178. package/src/templates/lytx-shared.ts +662 -0
  179. package/src/templates/lytxpixel-core.ts +144 -0
  180. package/src/templates/lytxpixel.ts +267 -0
  181. package/src/templates/lytxpixelBrowser.js +634 -0
  182. package/src/templates/lytxpixelBrowser.mjs +634 -0
  183. package/src/templates/parseData.js +12 -0
  184. package/src/templates/script.ts +31 -0
  185. package/src/templates/template.tsx +50 -0
  186. package/src/templates/test.js +3 -0
  187. package/src/templates/trackWebEvents.ts +177 -0
  188. package/src/templates/vendors/clickcease.ts +8 -0
  189. package/src/templates/vendors/google.ts +174 -0
  190. package/src/templates/vendors/linkedin.ts +23 -0
  191. package/src/templates/vendors/meta.ts +56 -0
  192. package/src/templates/vendors/quantcast.ts +22 -0
  193. package/src/templates/vendors/simplfi.ts +7 -0
  194. package/src/types/app-context.ts +16 -0
  195. package/src/utilities/dashboardParams.ts +188 -0
  196. package/src/utilities/dashboardQueries.ts +537 -0
  197. package/src/utilities/dashboardTransforms.ts +167 -0
  198. package/src/utilities/dataValidation.ts +414 -0
  199. package/src/utilities/detector.ts +73 -0
  200. package/src/utilities/encrypt.ts +103 -0
  201. package/src/utilities/index.ts +13 -0
  202. package/src/utilities/parser.ts +117 -0
  203. package/src/utilities/performanceMonitoring.ts +570 -0
  204. package/src/utilities/route_interuptors.ts +24 -0
  205. package/src/worker.tsx +675 -0
  206. package/tsconfig.json +78 -0
  207. package/types/env.d.ts +16 -0
  208. package/types/rw.d.ts +7 -0
  209. package/types/shims.d.ts +53 -0
  210. package/types/vite.d.ts +19 -0
  211. package/vite/vite-plugin-pixel-bundle.ts +126 -0
  212. package/vite.config.ts +53 -0
  213. package/worker-configuration.d.ts +8401 -0
@@ -0,0 +1,290 @@
1
+ import { MarketingLayout } from "@/app/components/marketing/MarketingLayout";
2
+
3
+ export function GetStarted() {
4
+ return (
5
+ <MarketingLayout>
6
+
7
+ <section className="pt-32 pb-16">
8
+ <div className="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
9
+ <span className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-amber-50 text-amber-700 text-xs font-semibold uppercase tracking-wider mb-6 border border-amber-100 dark:bg-amber-900/30 dark:text-amber-300 dark:border-amber-800">
10
+ Self-hosted on Cloudflare
11
+ </span>
12
+ <h1 className="text-4xl md:text-6xl font-bold tracking-tight text-slate-900 dark:text-white mb-6 leading-[1.1]">
13
+ Get started with Lytx in your Cloudflare account
14
+ </h1>
15
+ <p className="text-lg md:text-xl text-slate-600 dark:text-slate-400 max-w-[46rem] mx-auto leading-relaxed">
16
+ Deploy the open-source stack with Alchemy and own the data end to end. This is the free, self-hosted option.
17
+ </p>
18
+ </div>
19
+ </section>
20
+
21
+ <section className="pb-24">
22
+ <div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 grid grid-cols-1 lg:grid-cols-2 gap-12 items-start">
23
+ <div className="space-y-6">
24
+ <h2 className="text-2xl md:text-3xl font-bold text-slate-900 dark:text-white">
25
+ Use the Alchemy deploy script
26
+ </h2>
27
+ <p className="text-base md:text-lg text-slate-600 dark:text-slate-400 leading-relaxed">
28
+ Replace the placeholders with your Cloudflare resources and domain, then run your Alchemy deploy. This example mirrors `alchemy.run.ts` with safe placeholder names.
29
+ </p>
30
+ <div className="space-y-4 text-slate-600 dark:text-slate-400">
31
+ <div className="flex items-start gap-4">
32
+ <span className="flex h-8 w-8 items-center justify-center rounded-full bg-amber-500/10 text-amber-600 text-sm font-semibold">1</span>
33
+ <p className="flex-1">Set your app name and Cloudflare domain.</p>
34
+ </div>
35
+ <div className="flex items-start gap-4">
36
+ <span className="flex h-8 w-8 items-center justify-center rounded-full bg-amber-500/10 text-amber-600 text-sm font-semibold">2</span>
37
+ <p className="flex-1">Create your D1, KV, and Queue names.</p>
38
+ </div>
39
+ <div className="flex items-start gap-4">
40
+ <span className="flex h-8 w-8 items-center justify-center rounded-full bg-amber-500/10 text-amber-600 text-sm font-semibold">3</span>
41
+ <p className="flex-1">Set the required secrets in your environment before deploy.</p>
42
+ </div>
43
+ </div>
44
+ </div>
45
+ <div className="rounded-2xl bg-slate-900 p-6 border border-slate-800 shadow-2xl">
46
+ <div className="text-xs text-slate-400 mb-4 font-mono">alchemy.run.ts</div>
47
+ <pre className="text-slate-200 text-xs md:text-sm leading-relaxed overflow-x-auto font-mono">
48
+ <code className="block whitespace-pre">
49
+ <span className="text-cyan-300">import</span>{" "}
50
+ <span className="text-slate-200">type</span>{" "}
51
+ <span className="text-slate-200">&#123;</span>{" "}
52
+ <span className="text-amber-300">SiteDurableObject</span>{" "}
53
+ <span className="text-slate-200">&#125;</span>{" "}
54
+ <span className="text-cyan-300">from</span>{" "}
55
+ <span className="text-emerald-300">"./db/durable/siteDurableObject"</span>
56
+ <span className="text-slate-200">;</span>
57
+ <br />
58
+ <span className="text-cyan-300">import</span>{" "}
59
+ <span className="text-sky-200">alchemy</span>{" "}
60
+ <span className="text-cyan-300">from</span>{" "}
61
+ <span className="text-emerald-300">"alchemy"</span>
62
+ <span className="text-slate-200">;</span>
63
+ <br />
64
+ <span className="text-cyan-300">import</span>{" "}
65
+ <span className="text-slate-200">&#123;</span>{" "}
66
+ <span className="text-sky-200">D1Database</span>
67
+ <span className="text-slate-200">,</span>{" "}
68
+ <span className="text-sky-200">KVNamespace</span>
69
+ <span className="text-slate-200">,</span>{" "}
70
+ <span className="text-sky-200">DurableObjectNamespace</span>
71
+ <span className="text-slate-200">,</span>{" "}
72
+ <span className="text-sky-200">Redwood</span>
73
+ <span className="text-slate-200">,</span>{" "}
74
+ <span className="text-sky-200">Queue</span>{" "}
75
+ <span className="text-slate-200">&#125;</span>{" "}
76
+ <span className="text-cyan-300">from</span>{" "}
77
+ <span className="text-emerald-300">"alchemy/cloudflare"</span>
78
+ <span className="text-slate-200">;</span>
79
+ <br />
80
+ <br />
81
+ <span className="text-cyan-300">const</span>{" "}
82
+ <span className="text-slate-200">app = </span>
83
+ <span className="text-cyan-300">await</span>{" "}
84
+ <span className="text-sky-200">alchemy</span>
85
+ <span className="text-slate-200">(</span>
86
+ <span className="text-emerald-300">"your-app-name"</span>
87
+ <span className="text-slate-200">);</span>
88
+ <br />
89
+ <span className="text-cyan-300">const</span>{" "}
90
+ <span className="text-slate-200">adoptMode = </span>
91
+ <span className="text-purple-300">true</span>
92
+ <span className="text-slate-200">;</span>
93
+ <br />
94
+ <br />
95
+ <span className="text-cyan-300">const</span>{" "}
96
+ <span className="text-slate-200">siteDurableObject = </span>
97
+ <span className="text-sky-200">DurableObjectNamespace</span>
98
+ <span className="text-slate-200">&lt;</span>
99
+ <span className="text-amber-300">SiteDurableObject</span>
100
+ <span className="text-slate-200">&gt;(</span>
101
+ <span className="text-emerald-300">"site-durable-object"</span>
102
+ <span className="text-slate-200">, </span>
103
+ <span className="text-slate-200">&#123;</span>
104
+ <br />
105
+ <span className="text-slate-200">{" "}className: </span>
106
+ <span className="text-emerald-300">"SiteDurableObject"</span>
107
+ <span className="text-slate-200">,</span>
108
+ <br />
109
+ <span className="text-slate-200">{" "}sqlite: </span>
110
+ <span className="text-purple-300">true</span>
111
+ <span className="text-slate-200">,</span>
112
+ <br />
113
+ <span className="text-slate-200">&#125;);</span>
114
+ <br />
115
+ <br />
116
+ <span className="text-cyan-300">const</span>{" "}
117
+ <span className="text-slate-200">lytxEvents = </span>
118
+ <span className="text-cyan-300">await</span>{" "}
119
+ <span className="text-sky-200">KVNamespace</span>
120
+ <span className="text-slate-200">(</span>
121
+ <span className="text-emerald-300">"lytx-events"</span>
122
+ <span className="text-slate-200">, &#123; adopt: adoptMode, delete: </span>
123
+ <span className="text-purple-300">false</span>
124
+ <span className="text-slate-200"> &#125;);</span>
125
+ <br />
126
+ <span className="text-cyan-300">const</span>{" "}
127
+ <span className="text-slate-200">lytxConfig = </span>
128
+ <span className="text-cyan-300">await</span>{" "}
129
+ <span className="text-sky-200">KVNamespace</span>
130
+ <span className="text-slate-200">(</span>
131
+ <span className="text-emerald-300">"lytx-config"</span>
132
+ <span className="text-slate-200">, &#123; adopt: adoptMode, delete: </span>
133
+ <span className="text-purple-300">false</span>
134
+ <span className="text-slate-200"> &#125;);</span>
135
+ <br />
136
+ <span className="text-cyan-300">const</span>{" "}
137
+ <span className="text-slate-200">lytxSessions = </span>
138
+ <span className="text-cyan-300">await</span>{" "}
139
+ <span className="text-sky-200">KVNamespace</span>
140
+ <span className="text-slate-200">(</span>
141
+ <span className="text-emerald-300">"lytx-sessions"</span>
142
+ <span className="text-slate-200">, &#123; adopt: adoptMode, delete: </span>
143
+ <span className="text-purple-300">false</span>
144
+ <span className="text-slate-200"> &#125;);</span>
145
+ <br />
146
+ <span className="text-cyan-300">const</span>{" "}
147
+ <span className="text-slate-200">siteEventsQueue = </span>
148
+ <span className="text-cyan-300">await</span>{" "}
149
+ <span className="text-sky-200">Queue</span>
150
+ <span className="text-slate-200">(</span>
151
+ <span className="text-emerald-300">"site-events-queue"</span>
152
+ <span className="text-slate-200">, &#123; adopt: adoptMode, delete: </span>
153
+ <span className="text-purple-300">false</span>
154
+ <span className="text-slate-200"> &#125;);</span>
155
+ <br />
156
+ <br />
157
+ <span className="text-cyan-300">const</span>{" "}
158
+ <span className="text-slate-200">lytxCoreDb = </span>
159
+ <span className="text-cyan-300">await</span>{" "}
160
+ <span className="text-sky-200">D1Database</span>
161
+ <span className="text-slate-200">(</span>
162
+ <span className="text-emerald-300">"lytx-core-db"</span>
163
+ <span className="text-slate-200">, &#123;</span>
164
+ <br />
165
+ <span className="text-slate-200">{" "}name: </span>
166
+ <span className="text-emerald-300">"lytx-core-db"</span>
167
+ <span className="text-slate-200">,</span>
168
+ <br />
169
+ <span className="text-slate-200">{" "}migrationsDir: </span>
170
+ <span className="text-emerald-300">"./db/d1/migrations"</span>
171
+ <span className="text-slate-200">,</span>
172
+ <br />
173
+ <span className="text-slate-200">{" "}adopt: adoptMode,</span>
174
+ <br />
175
+ <span className="text-slate-200">{" "}delete: </span>
176
+ <span className="text-purple-300">false</span>
177
+ <span className="text-slate-200">,</span>
178
+ <br />
179
+ <span className="text-slate-200">&#125;);</span>
180
+ <br />
181
+ <br />
182
+ <span className="text-cyan-300">export</span>{" "}
183
+ <span className="text-cyan-300">const</span>{" "}
184
+ <span className="text-slate-200">worker = </span>
185
+ <span className="text-cyan-300">await</span>{" "}
186
+ <span className="text-sky-200">Redwood</span>
187
+ <span className="text-slate-200">(</span>
188
+ <span className="text-emerald-300">"lytx"</span>
189
+ <span className="text-slate-200">, &#123;</span>
190
+ <br />
191
+ <span className="text-slate-200">{" "}adopt: </span>
192
+ <span className="text-purple-300">true</span>
193
+ <span className="text-slate-200">,</span>
194
+ <br />
195
+ <span className="text-slate-200">{" "}domains: [&#123; adopt: </span>
196
+ <span className="text-purple-300">true</span>
197
+ <span className="text-slate-200">, domainName: </span>
198
+ <span className="text-emerald-300">"analytics.your-domain.com"</span>
199
+ <span className="text-slate-200"> &#125;],</span>
200
+ <br />
201
+ <span className="text-slate-200">{" "}wrangler: &#123;</span>
202
+ <br />
203
+ <span className="text-slate-200">{" "}main: </span>
204
+ <span className="text-emerald-300">"src/worker.tsx"</span>
205
+ <span className="text-slate-200">,</span>
206
+ <br />
207
+ <span className="text-slate-200">{" "}transform: (spec) =&gt; (&#123;</span>
208
+ <br />
209
+ <span className="text-slate-200">{" "}...spec,</span>
210
+ <br />
211
+ <span className="text-slate-200">{" "}compatibility_flags: [</span>
212
+ <span className="text-emerald-300">"nodejs_compat"</span>
213
+ <span className="text-slate-200">],</span>
214
+ <br />
215
+ <span className="text-slate-200">{" "}&#125;),</span>
216
+ <br />
217
+ <span className="text-slate-200">{" "}&#125;,</span>
218
+ <br />
219
+ <span className="text-slate-200">{" "}bindings: &#123;</span>
220
+ <br />
221
+ <span className="text-slate-200">{" "}SITE_DURABLE_OBJECT: siteDurableObject,</span>
222
+ <br />
223
+ <span className="text-slate-200">{" "}LYTX_EVENTS: lytxEvents,</span>
224
+ <br />
225
+ <span className="text-slate-200">{" "}lytx_config: lytxConfig,</span>
226
+ <br />
227
+ <span className="text-slate-200">{" "}lytx_sessions: lytxSessions,</span>
228
+ <br />
229
+ <span className="text-slate-200">{" "}lytx_core_db: lytxCoreDb,</span>
230
+ <br />
231
+ <span className="text-slate-200">{" "}SITE_EVENTS_QUEUE: siteEventsQueue,</span>
232
+ <br />
233
+ <span className="text-slate-200">{" "}LYTX_DOMAIN: process.env.LYTX_DOMAIN,</span>
234
+ <br />
235
+ <span className="text-slate-200">{" "}BETTER_AUTH_SECRET: alchemy.secret(process.env.BETTER_AUTH_SECRET),</span>
236
+ <br />
237
+ <span className="text-slate-200">{" "}GITHUB_CLIENT_SECRET: alchemy.secret(process.env.GITHUB_CLIENT_SECRET),</span>
238
+ <br />
239
+ <span className="text-slate-200">{" "}GOOGLE_CLIENT_SECRET: alchemy.secret(process.env.GOOGLE_CLIENT_SECRET),</span>
240
+ <br />
241
+ <span className="text-slate-200">{" "}RESEND_API_KEY: alchemy.secret(process.env.RESEND_API_KEY),</span>
242
+ <br />
243
+ <span className="text-slate-200">{" "}ENCRYPTION_KEY: alchemy.secret(process.env.ENCRYPTION_KEY),</span>
244
+ <br />
245
+ <span className="text-slate-200">{" "}&#125;,</span>
246
+ <br />
247
+ <span className="text-slate-200">&#125;);</span>
248
+ </code>
249
+ </pre>
250
+ <div className="mt-4 text-xs text-slate-400">
251
+ Source code:{" "}
252
+ <a
253
+ href="https://github.com/lytx-io/kit"
254
+ className="text-amber-300 hover:text-amber-200"
255
+ target="_blank"
256
+ rel="noreferrer"
257
+ >
258
+ github.com/lytx-io/kit
259
+ </a>
260
+ </div>
261
+ </div>
262
+ </div>
263
+ </section>
264
+
265
+ <section className="py-16 border-t border-slate-200 dark:border-slate-800">
266
+ <div className="max-w-4xl mx-auto px-4 text-center">
267
+ <h2 className="text-3xl font-bold text-slate-900 dark:text-white mb-4">Need help with deployment?</h2>
268
+ <p className="text-slate-600 dark:text-slate-400 mb-8">
269
+ We can walk you through setup and verify your Cloudflare resources before launch.
270
+ </p>
271
+ <div className="flex flex-col sm:flex-row justify-center gap-4">
272
+ <a
273
+ href="https://github.com/lytx-io/kit"
274
+ className="inline-flex items-center justify-center px-6 py-3 rounded-full border border-slate-200 text-slate-900 font-semibold hover:bg-slate-50 dark:border-slate-700 dark:text-white dark:hover:bg-slate-800 transition-colors"
275
+ >
276
+ View the repo
277
+ </a>
278
+ <a
279
+ href="/signup"
280
+ className="inline-flex items-center justify-center px-6 py-3 rounded-full bg-orange-600 text-white font-semibold hover:bg-orange-500 transition-colors shadow-lg shadow-orange-500/20"
281
+ >
282
+ Create an account
283
+ </a>
284
+ </div>
285
+ </div>
286
+ </section>
287
+
288
+ </MarketingLayout>
289
+ );
290
+ }
@@ -0,0 +1,268 @@
1
+ import { MarketingLayout } from "@/app/components/marketing/MarketingLayout";
2
+ import { SectionHeading } from "@/app/components/marketing/SectionHeading";
3
+ import { CheckIcon } from "@/app/components/marketing/CheckIcon";
4
+
5
+ export function Home() {
6
+ return (
7
+ <MarketingLayout navLinks={[{ label: "Features", href: "#features" }, { label: "Get Started", href: "/signup" }, { label: "FAQ", href: "#faq" }]}>
8
+
9
+ {/* Hero Section */}
10
+ <section className="relative pt-28 sm:pt-32 pb-12 sm:pb-20 overflow-hidden">
11
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10 text-center">
12
+ <div className="mx-auto max-w-4xl">
13
+ <h1 className="text-5xl md:text-7xl font-bold tracking-tight text-slate-900 dark:text-white mb-8 leading-[1.1]">
14
+ Analytics for <br />
15
+ <span className="text-transparent bg-clip-text bg-gradient-to-r from-amber-400 to-orange-500">
16
+ builders & creators
17
+ </span>
18
+ </h1>
19
+ <p className="mt-4 text-xl text-slate-600 dark:text-slate-400 mb-8 max-w-[46rem] mx-auto leading-relaxed">
20
+ Open-source, privacy-first web analytics. All the insights you need, none of the tracking you don't.
21
+ </p>
22
+
23
+ {/* Pills */}
24
+ <div className="flex flex-wrap justify-center gap-2 mb-10">
25
+ {["Privacy-first", "Cookie-less", "Open Source", "Self-hostable", "Lightweight"].map((badge) => (
26
+ <span key={badge} className="px-3 py-1 rounded-full bg-slate-100 text-slate-600 text-xs font-medium border border-slate-200 dark:bg-slate-900 dark:border-slate-800 dark:text-slate-400">
27
+ {badge}
28
+ </span>
29
+ ))}
30
+ </div>
31
+
32
+ <div className="flex flex-col sm:flex-row justify-center gap-4 mb-4">
33
+ <a
34
+ href="/signup"
35
+ className="inline-flex items-center justify-center px-8 py-3.5 text-base font-semibold rounded-full text-white bg-brand-cta hover:bg-brand-cta-hover transition-all"
36
+ >
37
+ Get Started
38
+ </a>
39
+ </div>
40
+ <p className="text-sm text-slate-500 dark:text-slate-400 mb-16">
41
+ Cloud from $19/mo · Self-host free
42
+ </p>
43
+ </div>
44
+
45
+ {/* Hero Image */}
46
+ <div className="relative mx-auto max-w-5xl">
47
+ <div className="rounded-xl bg-slate-900/5 p-2 ring-1 ring-inset ring-slate-900/10 lg:rounded-2xl lg:p-3 dark:bg-white/5 dark:ring-white/10 backdrop-blur-sm">
48
+ <img
49
+ src="/images/lytx_light_dashboard.png"
50
+ alt="Lytx Dashboard"
51
+ className="w-full rounded-lg shadow-2xl ring-1 ring-slate-900/10 dark:ring-black/20 block dark:hidden"
52
+ />
53
+ <img
54
+ src="/images/lytx_dark_dashboard.png"
55
+ alt="Lytx Dashboard"
56
+ className="w-full rounded-lg shadow-2xl ring-1 ring-slate-900/10 dark:ring-black/20 hidden dark:block"
57
+ />
58
+ </div>
59
+ {/* Glow effects */}
60
+ <div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-full h-full -z-10 bg-amber-500/20 blur-[100px] rounded-full opacity-50 pointer-events-none"></div>
61
+ </div>
62
+ </div>
63
+ </section>
64
+
65
+ {/* Features - Alternating Layout */}
66
+ <section id="features" className="py-16 sm:py-24 space-y-32 scroll-mt-20">
67
+ {/* Feature 1 */}
68
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
69
+ <div className="flex flex-col lg:flex-row items-center gap-16">
70
+ <div className="flex-1 min-w-0 w-full order-2 lg:order-1">
71
+ <div className="rounded-xl bg-slate-100 p-5 sm:p-8 border border-slate-200 dark:bg-slate-900 dark:border-slate-800 shadow-xl">
72
+ {/* Abstract representation of analytics */}
73
+ <div className="space-y-4">
74
+ <div className="h-4 w-3/4 bg-slate-200 dark:bg-slate-800 rounded"></div>
75
+ <div className="h-32 w-full bg-amber-50 dark:bg-amber-900/20 rounded-lg border border-amber-100 dark:border-amber-900/50 flex items-end p-4 gap-2">
76
+ <div className="w-full bg-amber-400/20 rounded-t h-[40%]"></div>
77
+ <div className="w-full bg-amber-400/40 rounded-t h-[70%]"></div>
78
+ <div className="w-full bg-amber-400/60 rounded-t h-[50%]"></div>
79
+ <div className="w-full bg-amber-400/80 rounded-t h-[90%]"></div>
80
+ <div className="w-full bg-amber-500 rounded-t h-[60%]"></div>
81
+ </div>
82
+ <div className="flex gap-4">
83
+ <div className="h-20 w-1/2 bg-slate-200 dark:bg-slate-800 rounded"></div>
84
+ <div className="h-20 w-1/2 bg-slate-200 dark:bg-slate-800 rounded"></div>
85
+ </div>
86
+ </div>
87
+ </div>
88
+ </div>
89
+ <div className="flex-1 min-w-0 w-full order-1 lg:order-2">
90
+ <div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-amber-50 text-amber-700 text-xs font-semibold uppercase tracking-wider mb-6 border border-amber-100 dark:bg-amber-900/30 dark:text-amber-300 dark:border-amber-800">
91
+ <span className="w-2 h-2 rounded-full bg-amber-500"></span>
92
+ Web Analytics
93
+ </div>
94
+ <h3 className="text-3xl font-bold text-slate-900 dark:text-white mb-6">
95
+ All your metrics in one place
96
+ </h3>
97
+ <p className="text-lg text-slate-600 dark:text-slate-400 leading-relaxed mb-8">
98
+ Get a comprehensive view of your website's performance. Track visitors, page views, bounce rates, and more without getting lost in complicated menus.
99
+ </p>
100
+ <ul className="space-y-4">
101
+ {[
102
+ "Real-time visitor counts",
103
+ "Top pages and referrers",
104
+ "Device and location data",
105
+ ].map((item) => (
106
+ <li key={item} className="flex items-start gap-3 text-slate-600 dark:text-slate-400">
107
+ <CheckIcon className="w-5 h-5 text-amber-500 mt-0.5 flex-shrink-0" />
108
+ <span>{item}</span>
109
+ </li>
110
+ ))}
111
+ </ul>
112
+ </div>
113
+ </div>
114
+ </div>
115
+
116
+ {/* Feature 2 */}
117
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
118
+ <div className="flex flex-col lg:flex-row items-center gap-16">
119
+ <div className="flex-1 min-w-0 w-full">
120
+ <div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-orange-50 text-orange-700 text-xs font-semibold uppercase tracking-wider mb-6 border border-orange-100 dark:bg-orange-900/30 dark:text-orange-300 dark:border-orange-800">
121
+ <span className="w-2 h-2 rounded-full bg-orange-500"></span>
122
+ Product Analytics
123
+ </div>
124
+ <h3 className="text-3xl font-bold text-slate-900 dark:text-white mb-6">
125
+ Understand user journeys
126
+ </h3>
127
+ <p className="text-lg text-slate-600 dark:text-slate-400 leading-relaxed mb-8">
128
+ See exactly how users interact with your product. Identify drop-off points, optimize conversion funnels, and make data-driven decisions.
129
+ </p>
130
+ <ul className="space-y-4">
131
+ {[
132
+ "Custom event tracking",
133
+ "Funnel analysis",
134
+ "Retention cohorts",
135
+ "User flow visualization"
136
+ ].map((item) => (
137
+ <li key={item} className="flex items-start gap-3 text-slate-600 dark:text-slate-400">
138
+ <CheckIcon className="w-5 h-5 text-orange-500 mt-0.5 flex-shrink-0" />
139
+ <span>{item}</span>
140
+ </li>
141
+ ))}
142
+ </ul>
143
+ </div>
144
+ <div className="flex-1 min-w-0 w-full">
145
+ <div className="rounded-xl bg-slate-100 p-5 sm:p-8 border border-slate-200 dark:bg-slate-900 dark:border-slate-800 shadow-xl">
146
+ {/* Abstract representation of funnels */}
147
+ <div className="space-y-6">
148
+ <div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-3 sm:gap-4">
149
+ <div className="w-full sm:flex-1 min-w-0 bg-orange-500 h-12 rounded opacity-100 flex items-center justify-center text-white font-bold text-sm">100%</div>
150
+ <div className="self-center w-1 h-5 sm:w-16 sm:h-1 bg-slate-300 dark:bg-slate-700"></div>
151
+ <div className="w-full sm:flex-1 min-w-0 bg-orange-500 h-12 rounded opacity-75 flex items-center justify-center text-white font-bold text-sm">75%</div>
152
+ <div className="self-center w-1 h-5 sm:w-16 sm:h-1 bg-slate-300 dark:bg-slate-700"></div>
153
+ <div className="w-full sm:flex-1 min-w-0 bg-orange-500 h-12 rounded opacity-50 flex items-center justify-center text-white font-bold text-sm">40%</div>
154
+ </div>
155
+ <div className="h-32 w-full bg-slate-200 dark:bg-slate-800 rounded-lg"></div>
156
+ </div>
157
+ </div>
158
+ </div>
159
+ </div>
160
+ </div>
161
+ </section>
162
+
163
+ {/* Trust Grid */}
164
+ <section className="py-24 bg-slate-50 dark:bg-slate-900/30 border-y border-slate-200 dark:border-slate-800">
165
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
166
+ <SectionHeading title="Privacy is our business" align="left" />
167
+
168
+ <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
169
+ {[
170
+ { title: "Privacy First", desc: "No IP tracking, no fingerprinting, no cookies. Compliant with GDPR, CCPA, and PECR." },
171
+ { title: "Data Ownership", desc: "You own your data. Export it anytime. We never sell or share your data with third parties." },
172
+ { title: "Open Source", desc: "Our code is public. You can audit exactly what we do and how we handle your data." },
173
+ { title: "Lightweight", desc: "Our script is under 5kb. It loads asynchronously and never slows down your website." }
174
+ ].map((card, i) => (
175
+ <div key={i} className="p-6 bg-white dark:bg-slate-900 rounded-2xl border border-slate-200 dark:border-slate-800">
176
+ <h3 className="font-bold text-slate-900 dark:text-white mb-2">{card.title}</h3>
177
+ <p className="text-sm text-slate-600 dark:text-slate-400">{card.desc}</p>
178
+ </div>
179
+ ))}
180
+ </div>
181
+ </div>
182
+ </section>
183
+
184
+ {/* Integration */}
185
+ <section className="py-24">
186
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
187
+ <div className="bg-slate-900 rounded-3xl p-8 md:p-16 text-center md:text-left relative overflow-hidden">
188
+ {/* Background decoration */}
189
+ <div className="absolute top-0 right-0 -mr-20 -mt-20 w-96 h-96 bg-amber-500/20 blur-3xl rounded-full"></div>
190
+
191
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-12 items-center relative z-10">
192
+ <div>
193
+ <h2 className="text-3xl md:text-4xl font-bold text-white mb-6">
194
+ Install in seconds
195
+ </h2>
196
+ <p className="text-lg text-slate-400 mb-8">
197
+ Just add our lightweight snippet to your website's <code className="bg-white/10 px-1.5 py-0.5 rounded text-white font-mono text-sm">&lt;head&gt;</code> and you're good to go. It works with any framework.
198
+ </p>
199
+ <div className="flex flex-wrap gap-4">
200
+ {/* Framework Logos (Text for now) */}
201
+ {["React", "Vue", "Next.js", "WordPress", "Shopify"].map(fw => (
202
+ <span key={fw} className="px-3 py-1 rounded-md bg-white/10 text-white/80 text-sm">{fw}</span>
203
+ ))}
204
+ </div>
205
+ </div>
206
+ <div className="bg-black/50 rounded-xl p-6 border border-white/10 font-mono text-sm text-left shadow-2xl overflow-x-auto">
207
+ <div className="flex gap-1.5 mb-4">
208
+ <div className="w-3 h-3 rounded-full bg-red-500/50"></div>
209
+ <div className="w-3 h-3 rounded-full bg-yellow-500/50"></div>
210
+ <div className="w-3 h-3 rounded-full bg-green-500/50"></div>
211
+ </div>
212
+ <pre className="text-slate-300">
213
+ <code>
214
+ <span className="text-orange-300">&lt;script</span> <span className="text-amber-300">defer</span> <span className="text-amber-300">data-domain</span>=<span className="text-yellow-200">"yoursite.com"</span> <span className="text-amber-300">src</span>=<span className="text-yellow-200">"https://lytx.app/script.js"</span><span className="text-orange-300">&gt;&lt;/script&gt;</span>
215
+ </code>
216
+ </pre>
217
+ </div>
218
+ </div>
219
+ </div>
220
+ </div>
221
+ </section>
222
+
223
+ {/* FAQ */}
224
+ <section id="faq" className="py-24 bg-slate-50 dark:bg-slate-900/30 scroll-mt-20">
225
+ <div className="max-w-[64rem] mx-auto px-4 sm:px-6 lg:px-8">
226
+ <SectionHeading title="Frequently Asked Questions" />
227
+
228
+ <div className="space-y-6">
229
+ {[
230
+ { q: "Is Lytx GDPR compliant?", a: "Yes, fully. We don't use cookies and we don't collect any personal data. You don't even need a cookie banner." },
231
+ { q: "Can I self-host Lytx?", a: "Absolutely. Lytx is open-source. You can self-host on Cloudflare with our Alchemy deploy script." },
232
+ { q: "Will it slow down my site?", a: "No. Our script is under 5kb and loads asynchronously, so it doesn't block your page rendering." },
233
+ { q: "How is this different from Google Analytics?", a: "GA is complex, invasive, and sells your data. Lytx is simple, private, and yours." }
234
+ ].map((item, i) => (
235
+ <div key={i} className="bg-white dark:bg-slate-900 rounded-2xl p-6 border border-slate-200 dark:border-slate-800">
236
+ <h3 className="font-bold text-slate-900 dark:text-white mb-2">{item.q}</h3>
237
+ <p className="text-slate-600 dark:text-slate-400">{item.a}</p>
238
+ </div>
239
+ ))}
240
+ </div>
241
+ </div>
242
+ </section>
243
+
244
+ {/* Final CTA */}
245
+ <section className="py-24 text-center">
246
+ <div className="max-w-[48rem] mx-auto px-4">
247
+ <h2 className="text-4xl font-bold text-slate-900 dark:text-white mb-6">Ready to take control of your data?</h2>
248
+ <div className="flex flex-col sm:flex-row justify-center gap-4">
249
+ <a
250
+ href="/signup"
251
+ className="inline-flex items-center justify-center px-8 py-4 text-lg font-semibold rounded-full text-white bg-slate-900 hover:bg-slate-800 dark:bg-white dark:text-black dark:hover:bg-slate-200 transition-all shadow-lg"
252
+ >
253
+ Sign Up
254
+ </a>
255
+ <a
256
+ href="/get-started"
257
+ className="inline-flex items-center justify-center px-8 py-4 text-lg font-semibold rounded-full text-slate-900 border border-slate-200 hover:bg-slate-50 dark:text-white dark:border-slate-700 dark:hover:bg-slate-800 transition-all"
258
+ >
259
+ Self-Host on Cloudflare
260
+ </a>
261
+ </div>
262
+
263
+ </div>
264
+ </section>
265
+
266
+ </MarketingLayout>
267
+ );
268
+ }