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
package/cli/index.ts ADDED
@@ -0,0 +1,609 @@
1
+ import { CliRenderer, BoxRenderable, TextRenderable, createCliRenderer, type ParsedKey } from "@opentui/core"
2
+ import { getKeyHandler } from "@opentui/core"
3
+ export function setupCommonDemoKeys(renderer: CliRenderer) {
4
+ getKeyHandler().on("keypress", (key: ParsedKey) => {
5
+ if (key.name === "`" || key.name === '"') {
6
+ renderer.console.toggle()
7
+ } else if (key.name === "t") {
8
+ renderer.toggleDebugOverlay()
9
+ } else if (key.name === "g" && key.ctrl) {
10
+ console.log("dumping hit grid")
11
+ renderer.dumpHitGrid()
12
+ } else if (key.name === "l" && key.shift) {
13
+ renderer.start()
14
+ } else if (key.name === "s" && key.shift) {
15
+ renderer.stop()
16
+ } else if (key.name === "a" && key.shift) {
17
+ renderer.auto()
18
+ }
19
+ })
20
+ }
21
+
22
+ interface LayoutDemo {
23
+ name: string
24
+ description: string
25
+ setup: () => void
26
+ }
27
+
28
+ let renderer: CliRenderer | null = null
29
+ let header: BoxRenderable | null = null
30
+ let headerText: TextRenderable | null = null
31
+ let contentArea: BoxRenderable | null = null
32
+ let sidebar: BoxRenderable | null = null
33
+ let sidebarText: TextRenderable | null = null
34
+ let mainContent: BoxRenderable | null = null
35
+ let mainContentText: TextRenderable | null = null
36
+ let rightSidebar: BoxRenderable | null = null
37
+ let rightSidebarText: TextRenderable | null = null
38
+ let footer: BoxRenderable | null = null
39
+ let footerText: TextRenderable | null = null
40
+ let moveableElement: BoxRenderable | null = null
41
+ let moveableText: TextRenderable | null = null
42
+ let absolutePositionedBox: BoxRenderable | null = null
43
+ let absolutePositionedText: TextRenderable | null = null
44
+ let currentDemoIndex = 0
45
+ let autoAdvanceTimeout: Timer | null = null
46
+ let autoplayEnabled = true
47
+ let moveableElementVisible = true
48
+ let moveableElementX = 0
49
+ let moveableElementY = 0
50
+
51
+ const layoutDemos: LayoutDemo[] = [
52
+ {
53
+ name: "Horizontal Layout",
54
+ description: "Sidebar on left, main content on right",
55
+ setup: () => setupHorizontalLayout(),
56
+ },
57
+ {
58
+ name: "Vertical Layout",
59
+ description: "Sidebar on top, main content below",
60
+ setup: () => setupVerticalLayout(),
61
+ },
62
+ {
63
+ name: "Centered Layout",
64
+ description: "Content centered with margins",
65
+ setup: () => setupCenteredLayout(),
66
+ },
67
+ {
68
+ name: "Three Column",
69
+ description: "Left sidebar, center content, right sidebar",
70
+ setup: () => setupThreeColumnLayout(),
71
+ },
72
+ ]
73
+
74
+ function resetElementLayout(element: BoxRenderable): void {
75
+ element.flexBasis = "auto"
76
+ element.flexGrow = 0
77
+ element.flexShrink = 0
78
+ element.width = "auto"
79
+ element.height = "auto"
80
+
81
+ element.minWidth = undefined
82
+ element.maxWidth = undefined
83
+ element.minHeight = undefined
84
+ element.maxHeight = undefined
85
+ }
86
+
87
+ function setupHorizontalLayout(): void {
88
+ if (!contentArea || !sidebar || !mainContent || !rightSidebar) return
89
+
90
+ sidebar.visible = true
91
+ mainContent.visible = true
92
+ rightSidebar.visible = false
93
+
94
+ resetElementLayout(sidebar)
95
+ resetElementLayout(mainContent)
96
+
97
+ contentArea.flexDirection = "row"
98
+ contentArea.alignItems = "stretch"
99
+
100
+ const sidebarWidth = Math.max(15, Math.floor(renderer!.terminalWidth * 0.2))
101
+ sidebar.flexBasis = sidebarWidth
102
+ sidebar.flexGrow = 0
103
+ sidebar.flexShrink = 0
104
+ sidebar.width = sidebarWidth
105
+ sidebar.minWidth = 15
106
+ sidebar.height = "auto"
107
+ if (sidebarText) sidebarText.content = "LEFT SIDEBAR"
108
+ sidebar.backgroundColor = "#64748b"
109
+
110
+ mainContent.flexBasis = "auto"
111
+ mainContent.flexGrow = 1
112
+ mainContent.flexShrink = 1
113
+ mainContent.width = "auto"
114
+ mainContent.minWidth = 20
115
+ mainContent.height = "auto"
116
+ if (mainContentText) mainContentText.content = "MAIN CONTENT"
117
+ mainContent.backgroundColor = "#eab308"
118
+ }
119
+
120
+ function setupVerticalLayout(): void {
121
+ if (!contentArea || !sidebar || !mainContent || !rightSidebar) return
122
+
123
+ sidebar.visible = true
124
+ mainContent.visible = true
125
+ rightSidebar.visible = false
126
+
127
+ resetElementLayout(sidebar)
128
+ resetElementLayout(mainContent)
129
+
130
+ contentArea.flexDirection = "column"
131
+ contentArea.alignItems = "stretch"
132
+
133
+ const contentHeight = renderer!.terminalHeight - 6
134
+ const topBarHeight = Math.max(3, Math.floor(contentHeight * 0.2))
135
+ sidebar.flexBasis = topBarHeight
136
+ sidebar.flexGrow = 0
137
+ sidebar.flexShrink = 0
138
+ sidebar.height = topBarHeight
139
+ sidebar.minHeight = 3
140
+ sidebar.width = "auto"
141
+ if (sidebarText) sidebarText.content = "TOP BAR"
142
+ sidebar.backgroundColor = "#059669"
143
+
144
+ mainContent.flexBasis = "auto"
145
+ mainContent.flexGrow = 1
146
+ mainContent.flexShrink = 1
147
+ mainContent.height = "auto"
148
+ mainContent.minHeight = 5
149
+ mainContent.width = "auto"
150
+ if (mainContentText) mainContentText.content = "MAIN CONTENT"
151
+ mainContent.backgroundColor = "#eab308"
152
+ }
153
+
154
+ function setupCenteredLayout(): void {
155
+ if (!contentArea || !sidebar || !mainContent || !rightSidebar) return
156
+
157
+ sidebar.visible = false
158
+ mainContent.visible = true
159
+ rightSidebar.visible = false
160
+
161
+ resetElementLayout(mainContent)
162
+
163
+ contentArea.flexDirection = "row"
164
+ contentArea.alignItems = "stretch"
165
+ contentArea.justifyContent = "center"
166
+
167
+ const centerWidth = Math.max(30, Math.floor(renderer!.terminalWidth * 0.6))
168
+ mainContent.flexBasis = centerWidth
169
+ mainContent.flexGrow = 0
170
+ mainContent.flexShrink = 0
171
+ mainContent.width = centerWidth
172
+ mainContent.minWidth = 30
173
+ mainContent.maxWidth = Math.floor(renderer!.terminalWidth * 0.8)
174
+ mainContent.height = "auto"
175
+ if (mainContentText) mainContentText.content = "CENTERED CONTENT"
176
+ mainContent.backgroundColor = "#6b7280"
177
+ }
178
+
179
+ function setupThreeColumnLayout(): void {
180
+ if (!contentArea || !sidebar || !mainContent || !rightSidebar) return
181
+
182
+ sidebar.visible = true
183
+ mainContent.visible = true
184
+ rightSidebar.visible = true
185
+
186
+ resetElementLayout(sidebar)
187
+ resetElementLayout(mainContent)
188
+ resetElementLayout(rightSidebar)
189
+
190
+ contentArea.flexDirection = "row"
191
+ contentArea.alignItems = "stretch"
192
+
193
+ const terminalWidth = renderer!.terminalWidth
194
+ const sidebarWidth = Math.max(12, Math.floor(terminalWidth * 0.15))
195
+
196
+ sidebar.flexBasis = sidebarWidth
197
+ sidebar.flexGrow = 0
198
+ sidebar.flexShrink = 0
199
+ sidebar.width = sidebarWidth
200
+ sidebar.minWidth = 12
201
+ sidebar.height = "auto"
202
+ if (sidebarText) sidebarText.content = "LEFT"
203
+ sidebar.backgroundColor = "#dc2626"
204
+
205
+ mainContent.flexBasis = "auto"
206
+ mainContent.flexGrow = 1
207
+ mainContent.flexShrink = 1
208
+ mainContent.width = "auto"
209
+ mainContent.minWidth = 20
210
+ mainContent.height = "auto"
211
+ if (mainContentText) mainContentText.content = "CENTER"
212
+ mainContent.backgroundColor = "#059669"
213
+
214
+ rightSidebar.flexBasis = sidebarWidth
215
+ rightSidebar.flexGrow = 0
216
+ rightSidebar.flexShrink = 0
217
+ rightSidebar.width = sidebarWidth
218
+ rightSidebar.minWidth = 12
219
+ rightSidebar.height = "auto"
220
+ if (rightSidebarText) rightSidebarText.content = "RIGHT"
221
+ rightSidebar.backgroundColor = "#4b5563"
222
+ }
223
+
224
+ function createLayoutElements(rendererInstance: CliRenderer): void {
225
+ renderer = rendererInstance
226
+ renderer.setBackgroundColor("#001122")
227
+
228
+ header = new BoxRenderable(renderer, {
229
+ id: "header",
230
+ zIndex: 0,
231
+ width: "auto",
232
+ height: 3,
233
+ backgroundColor: "#3b82f6",
234
+ borderStyle: "single",
235
+ alignItems: "center",
236
+ border: true,
237
+ })
238
+
239
+ headerText = new TextRenderable(renderer, {
240
+ id: "header-text",
241
+ content: "LAYOUT DEMO",
242
+ fg: "#ffffff",
243
+ bg: "transparent",
244
+ zIndex: 1,
245
+ })
246
+
247
+ header.add(headerText)
248
+
249
+ contentArea = new BoxRenderable(renderer, {
250
+ id: "content-area",
251
+ zIndex: 0,
252
+ width: "auto",
253
+ height: "auto",
254
+ flexDirection: "row",
255
+ flexGrow: 1,
256
+ flexShrink: 1,
257
+ })
258
+
259
+ sidebar = new BoxRenderable(renderer, {
260
+ id: "sidebar",
261
+ zIndex: 0,
262
+ width: "auto",
263
+ height: "auto",
264
+ backgroundColor: "#64748b",
265
+ borderStyle: "single",
266
+ flexGrow: 0,
267
+ flexShrink: 0,
268
+ flexDirection: "row",
269
+ alignItems: "center",
270
+ justifyContent: "center",
271
+ border: true,
272
+ })
273
+
274
+ sidebarText = new TextRenderable(renderer, {
275
+ id: "sidebar-text",
276
+ content: "SIDEBAR",
277
+ fg: "#ffffff",
278
+ bg: "transparent",
279
+ zIndex: 1,
280
+ })
281
+
282
+ sidebar.add(sidebarText)
283
+
284
+ mainContent = new BoxRenderable(renderer, {
285
+ id: "main-content",
286
+ zIndex: 0,
287
+ width: "auto",
288
+ height: "auto",
289
+ backgroundColor: "#919599",
290
+ borderStyle: "single",
291
+ flexGrow: 1,
292
+ flexShrink: 1,
293
+ flexDirection: "row",
294
+ alignItems: "center",
295
+ justifyContent: "center",
296
+ border: true,
297
+ })
298
+
299
+ mainContentText = new TextRenderable(renderer, {
300
+ id: "main-content-text",
301
+ content: "MAIN CONTENT",
302
+ fg: "#1e293b",
303
+ bg: "transparent",
304
+ zIndex: 1,
305
+ })
306
+
307
+ mainContent.add(mainContentText)
308
+
309
+ rightSidebar = new BoxRenderable(renderer, {
310
+ id: "right-sidebar",
311
+ zIndex: 0,
312
+ width: "auto",
313
+ height: "auto",
314
+ backgroundColor: "#4b5563",
315
+ borderStyle: "single",
316
+ flexGrow: 0,
317
+ flexShrink: 0,
318
+ flexDirection: "row",
319
+ alignItems: "center",
320
+ justifyContent: "center",
321
+ border: true,
322
+ })
323
+
324
+ rightSidebarText = new TextRenderable(renderer, {
325
+ id: "right-sidebar-text",
326
+ content: "RIGHT",
327
+ fg: "#ffffff",
328
+ bg: "transparent",
329
+ zIndex: 1,
330
+ })
331
+
332
+ rightSidebar.add(rightSidebarText)
333
+
334
+ footer = new BoxRenderable(renderer, {
335
+ id: "footer",
336
+ zIndex: 0,
337
+ width: "auto",
338
+ height: 3,
339
+ backgroundColor: "#1e40af",
340
+ borderStyle: "single",
341
+ flexGrow: 0,
342
+ flexShrink: 0,
343
+ flexDirection: "row",
344
+ alignItems: "center",
345
+ justifyContent: "center",
346
+ border: true,
347
+ })
348
+
349
+ footerText = new TextRenderable(renderer, {
350
+ id: "footer-text",
351
+ content: "",
352
+ fg: "#ffffff",
353
+ bg: "transparent",
354
+ zIndex: 1,
355
+ })
356
+
357
+ footer.add(footerText)
358
+
359
+ moveableElement = new BoxRenderable(renderer, {
360
+ id: "moveable",
361
+ zIndex: 100,
362
+ width: 8,
363
+ height: 3,
364
+ backgroundColor: "#ff6b6b",
365
+ borderStyle: "single",
366
+ borderColor: "#ff4757",
367
+ position: "absolute",
368
+ left: 0,
369
+ top: 0,
370
+ flexDirection: "row",
371
+ alignItems: "center",
372
+ justifyContent: "center",
373
+ border: true,
374
+ })
375
+
376
+ moveableText = new TextRenderable(renderer, {
377
+ id: "moveable-text",
378
+ content: "MOVE",
379
+ fg: "#ffffff",
380
+ bg: "transparent",
381
+ zIndex: 101,
382
+ })
383
+
384
+ moveableElement.add(moveableText)
385
+
386
+ absolutePositionedBox = new BoxRenderable(renderer, {
387
+ id: "absolute-positioned-box",
388
+ zIndex: 150,
389
+ width: 20,
390
+ height: 3,
391
+ backgroundColor: "#22c55e",
392
+ borderStyle: "single",
393
+ borderColor: "#16a34a",
394
+ position: "absolute",
395
+ bottom: 1,
396
+ right: 1,
397
+ flexDirection: "row",
398
+ alignItems: "center",
399
+ justifyContent: "center",
400
+ border: true,
401
+ })
402
+
403
+ absolutePositionedText = new TextRenderable(renderer, {
404
+ id: "absolute-positioned-text",
405
+ content: "BOTTOM RIGHT",
406
+ fg: "#ffffff",
407
+ bg: "transparent",
408
+ zIndex: 151,
409
+ })
410
+
411
+ absolutePositionedBox.add(absolutePositionedText)
412
+
413
+ // Add all elements to contentArea in the correct order: left, center, right
414
+ contentArea.add(sidebar)
415
+ contentArea.add(mainContent)
416
+ contentArea.add(rightSidebar)
417
+
418
+ // Set initial visibility (rightSidebar is hidden for the first demo)
419
+ rightSidebar.visible = false
420
+
421
+ renderer.root.add(header)
422
+ renderer.root.add(contentArea)
423
+ renderer.root.add(footer)
424
+ renderer.root.add(moveableElement)
425
+ renderer.root.add(absolutePositionedBox)
426
+
427
+ centerMoveableElement()
428
+ updateFooterText()
429
+ renderer.on("resize", handleResize)
430
+ }
431
+
432
+ function handleResize(width: number, height: number): void {
433
+ // Root layout is automatically resized by the renderer
434
+ centerMoveableElement()
435
+ }
436
+
437
+ function handleKeyPress(key: ParsedKey): void {
438
+ switch (key.name) {
439
+ case "space": // Space - next layout
440
+ nextDemo()
441
+ break
442
+ case "r": // R - restart cycle
443
+ currentDemoIndex = 0
444
+ applyCurrentDemo()
445
+ break
446
+ case "p": // P - toggle autoplay
447
+ toggleAutoplay()
448
+ break
449
+ case "v": // V - toggle moveable element visibility
450
+ toggleMoveableElement()
451
+ break
452
+ case "w": // W - move up
453
+ moveMoveableElement(0, -1)
454
+ break
455
+ case "a": // A - move left
456
+ moveMoveableElement(-1, 0)
457
+ break
458
+ case "s": // S - move down
459
+ moveMoveableElement(0, 1)
460
+ break
461
+ case "d": // D - move right
462
+ moveMoveableElement(1, 0)
463
+ break
464
+ }
465
+ }
466
+
467
+ function nextDemo(): void {
468
+ currentDemoIndex = (currentDemoIndex + 1) % layoutDemos.length
469
+ applyCurrentDemo()
470
+ }
471
+
472
+ function toggleAutoplay(): void {
473
+ autoplayEnabled = !autoplayEnabled
474
+
475
+ if (autoplayEnabled) {
476
+ if (autoAdvanceTimeout) {
477
+ clearTimeout(autoAdvanceTimeout)
478
+ }
479
+ autoAdvanceTimeout = setTimeout(() => {
480
+ nextDemo()
481
+ }, 4000)
482
+ } else {
483
+ if (autoAdvanceTimeout) {
484
+ clearTimeout(autoAdvanceTimeout)
485
+ autoAdvanceTimeout = null
486
+ }
487
+ }
488
+
489
+ updateFooterText()
490
+ }
491
+
492
+ function toggleMoveableElement(): void {
493
+ if (!moveableElement) return
494
+
495
+ moveableElementVisible = !moveableElementVisible
496
+ moveableElement.visible = moveableElementVisible
497
+ updateFooterText()
498
+ }
499
+
500
+ function moveMoveableElement(deltaX: number, deltaY: number): void {
501
+ if (!moveableElement || !renderer) return
502
+
503
+ moveableElementX += deltaX
504
+ moveableElementY += deltaY
505
+
506
+ moveableElementX = Math.max(0, Math.min(renderer.terminalWidth - 8, moveableElementX))
507
+ moveableElementY = Math.max(0, Math.min(renderer.terminalHeight - 3, moveableElementY))
508
+
509
+ moveableElement.setPosition({
510
+ left: moveableElementX,
511
+ top: moveableElementY,
512
+ })
513
+ }
514
+
515
+ function centerMoveableElement(): void {
516
+ if (!renderer || !moveableElement) return
517
+
518
+ moveableElementX = Math.floor((renderer.terminalWidth - 8) / 2)
519
+ moveableElementY = Math.floor((renderer.terminalHeight - 3) / 2)
520
+
521
+ moveableElement.setPosition({
522
+ left: moveableElementX,
523
+ top: moveableElementY,
524
+ })
525
+ }
526
+
527
+ function updateFooterText(): void {
528
+ if (!footerText) return
529
+
530
+ const autoplayStatus = autoplayEnabled ? "ON" : "OFF"
531
+ const moveableStatus = moveableElementVisible ? "ON" : "OFF"
532
+ footerText.content = `SPACE: next | R: restart | P: autoplay (${autoplayStatus}) | V: overlay (${moveableStatus}) | WASD: move`
533
+ }
534
+
535
+ function applyCurrentDemo(): void {
536
+ const demo = layoutDemos[currentDemoIndex]
537
+ if (!headerText) return
538
+
539
+ const autoplayStatus = autoplayEnabled ? "AUTO" : "MANUAL"
540
+ headerText.content = `${demo.name} (${currentDemoIndex + 1}/${layoutDemos.length}) - ${autoplayStatus}`
541
+ demo.setup()
542
+
543
+ if (autoAdvanceTimeout) {
544
+ clearTimeout(autoAdvanceTimeout)
545
+ }
546
+
547
+ if (autoplayEnabled) {
548
+ autoAdvanceTimeout = setTimeout(() => {
549
+ nextDemo()
550
+ }, 4000)
551
+ }
552
+ }
553
+
554
+ export function run(rendererInstance: CliRenderer): void {
555
+ createLayoutElements(rendererInstance)
556
+ getKeyHandler().on("keypress", handleKeyPress)
557
+ currentDemoIndex = 0
558
+ applyCurrentDemo()
559
+ }
560
+
561
+ export function destroy(rendererInstance: CliRenderer): void {
562
+ if (autoAdvanceTimeout) {
563
+ clearTimeout(autoAdvanceTimeout)
564
+ autoAdvanceTimeout = null
565
+ }
566
+
567
+ getKeyHandler().off("keypress", handleKeyPress)
568
+
569
+ if (renderer) {
570
+ renderer.off("resize", handleResize)
571
+ }
572
+
573
+ if (header) rendererInstance.root.remove(header.id)
574
+ if (contentArea) rendererInstance.root.remove(contentArea.id)
575
+ if (footer) rendererInstance.root.remove(footer.id)
576
+ if (moveableElement) rendererInstance.root.remove(moveableElement.id)
577
+ if (absolutePositionedBox) rendererInstance.root.remove(absolutePositionedBox.id)
578
+
579
+ header = null
580
+ headerText = null
581
+ contentArea = null
582
+ sidebar = null
583
+ sidebarText = null
584
+ mainContent = null
585
+ mainContentText = null
586
+ rightSidebar = null
587
+ rightSidebarText = null
588
+ footer = null
589
+ footerText = null
590
+ moveableElement = null
591
+ moveableText = null
592
+ absolutePositionedBox = null
593
+ absolutePositionedText = null
594
+ renderer = null
595
+ currentDemoIndex = 0
596
+ moveableElementVisible = true
597
+ moveableElementX = 0
598
+ moveableElementY = 0
599
+ }
600
+
601
+ // if (import.meta.main) {
602
+ renderer = await createCliRenderer({
603
+ exitOnCtrlC: true,
604
+ targetFps: 30,
605
+ })
606
+ run(renderer)
607
+ setupCommonDemoKeys(renderer)
608
+ // renderer.start()
609
+ // }