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,88 @@
1
+ export type LytxResourceStagePosition = "prefix" | "suffix" | "none";
2
+
3
+ export type LytxResourceNames = {
4
+ appName: string;
5
+ workerName: string;
6
+ durableHostWorkerName: string;
7
+ durableObjectNamespaceName: string;
8
+ d1DatabaseName: string;
9
+ eventsKvNamespaceName: string;
10
+ configKvNamespaceName: string;
11
+ sessionsKvNamespaceName: string;
12
+ eventsQueueName: string;
13
+ };
14
+
15
+ export type LytxResourceNamingOptions = {
16
+ prefix?: string;
17
+ suffix?: string;
18
+ stage?: string;
19
+ stagePosition?: LytxResourceStagePosition;
20
+ separator?: "-" | "_";
21
+ overrides?: Partial<LytxResourceNames>;
22
+ };
23
+
24
+ export const DEFAULT_LYTX_RESOURCE_NAMES: LytxResourceNames = {
25
+ appName: "lytx",
26
+ workerName: "lytx-app",
27
+ durableHostWorkerName: "lytx-app-do-host",
28
+ durableObjectNamespaceName: "site-durable-object",
29
+ d1DatabaseName: "lytx-core-db",
30
+ eventsKvNamespaceName: "LYTX_EVENTS",
31
+ configKvNamespaceName: "lytx_config",
32
+ sessionsKvNamespaceName: "lytx_sessions",
33
+ eventsQueueName: "site-events-queue",
34
+ };
35
+
36
+ function normalizeSegment(value: string): string {
37
+ return value
38
+ .trim()
39
+ .replace(/\s+/g, "-")
40
+ .replace(/[^A-Za-z0-9_-]/g, "-")
41
+ .replace(/[-_]{2,}/g, "-")
42
+ .replace(/^[-_]+|[-_]+$/g, "");
43
+ }
44
+
45
+ function joinSegments(segments: Array<string | undefined>, separator: "-" | "_"): string {
46
+ const normalized = segments
47
+ .map((segment) => (segment ? normalizeSegment(segment) : ""))
48
+ .filter((segment) => segment.length > 0);
49
+ return normalized.join(separator);
50
+ }
51
+
52
+ export function resolveLytxResourceNames(
53
+ options: LytxResourceNamingOptions = {},
54
+ ): LytxResourceNames {
55
+ const separator = options.separator ?? "-";
56
+ const stagePosition = options.stagePosition ?? "none";
57
+ const stage_segment = options.stage ? normalizeSegment(options.stage) : "";
58
+ const prefix_segment = options.prefix ? normalizeSegment(options.prefix) : "";
59
+ const suffix_segment = options.suffix ? normalizeSegment(options.suffix) : "";
60
+
61
+ const withStrategy = (base: string): string => {
62
+ if (stagePosition === "prefix") {
63
+ return joinSegments([prefix_segment, stage_segment, base, suffix_segment], separator);
64
+ }
65
+ if (stagePosition === "suffix") {
66
+ return joinSegments([prefix_segment, base, stage_segment, suffix_segment], separator);
67
+ }
68
+ return joinSegments([prefix_segment, base, suffix_segment], separator);
69
+ };
70
+
71
+ const resolved: LytxResourceNames = {
72
+ ...DEFAULT_LYTX_RESOURCE_NAMES,
73
+ };
74
+
75
+ for (const [key, default_name] of Object.entries(DEFAULT_LYTX_RESOURCE_NAMES) as Array<
76
+ [keyof LytxResourceNames, string]
77
+ >) {
78
+ const override = options.overrides?.[key];
79
+ if (override) {
80
+ resolved[key] = normalizeSegment(override);
81
+ } else {
82
+ const strategy_name = withStrategy(default_name);
83
+ resolved[key] = strategy_name.length > 0 ? strategy_name : default_name;
84
+ }
85
+ }
86
+
87
+ return resolved;
88
+ }
@@ -0,0 +1,67 @@
1
+ import { env } from "cloudflare:workers";
2
+ import { IS_DEV } from "rwsdk/constants";
3
+
4
+ type Create_user = { data: { username: string } };
5
+ type User_Profile = {
6
+ id: string;
7
+ username: string;
8
+ createdAt: Date;
9
+ } | null
10
+ ;
11
+ type Create_credential = { data: { userId: string, credentialId: string, publicKey: Uint8Array<ArrayBufferLike>, counter: number } };
12
+
13
+ type DB_Credential = {
14
+ id: string;
15
+ createdAt: Date;
16
+ userId: string;
17
+ credentialId: string;
18
+ publicKey: Uint8Array;
19
+ counter: number;
20
+ } | null
21
+
22
+
23
+ //WARNING: This is a placeholder till we swap in db
24
+ export const db = {
25
+ user: {
26
+ create: async ({ data }: Create_user) => {
27
+ return { id: "test" }
28
+ },
29
+ findUnique: async ({ where }: { where: { id: string } }): Promise<User_Profile> => {
30
+ return {
31
+ id: "test",
32
+ createdAt: new Date(),
33
+ username: "test",
34
+ }
35
+ },
36
+ },
37
+ credential: {
38
+ create: async ({ data }: Create_credential) => {
39
+ },
40
+ //TODO:REPLACE With drizzle call
41
+ findUnique: async ({ where }: { where: { credentialId: string } }): Promise<DB_Credential> => {
42
+ return {
43
+ id: "test",
44
+ createdAt: new Date(),
45
+ userId: "test",
46
+ credentialId: "test",
47
+ publicKey: new Uint8Array(),
48
+ counter: 0,
49
+ }
50
+ },
51
+
52
+ //TODO:REPLACE With drizzle call
53
+ update: async ({ where, data }: { where: { credentialId: string }, data: { counter: number } }) => {
54
+ //TODO: Update the counter
55
+ if (IS_DEV) console.log(where, data);
56
+ }
57
+ },
58
+
59
+ }
60
+
61
+
62
+ export type User = User_Profile;
63
+
64
+ export const setupDb = async (cf_env: typeof env) => {
65
+ //TODO: Setup the db
66
+ if (IS_DEV) console.log(cf_env);
67
+ }
package/src/index.css ADDED
@@ -0,0 +1,285 @@
1
+ @import "tailwindcss";
2
+ @import "@fontsource/montserrat/400.css";
3
+ @import "@fontsource/montserrat/500.css";
4
+ @import "@fontsource/montserrat/600.css";
5
+ @import "@fontsource/montserrat/700.css";
6
+
7
+ @theme {
8
+ /* Color Palette - Light Mode */
9
+ --color-primary: #f97316;
10
+ --color-primary-hover: #ea580c;
11
+ --color-primary-light: #fdba74;
12
+
13
+ --color-brand-cta: #D95C00;
14
+ --color-brand-cta-hover: #c45300;
15
+
16
+ --color-secondary: #6b7280;
17
+ --color-secondary-hover: #4b5563;
18
+ --color-secondary-light: #9ca3af;
19
+
20
+ --color-accent: #f59e0b;
21
+ --color-accent-hover: #d97706;
22
+ --color-accent-light: #fcd34d;
23
+
24
+ --color-danger: #ef4444;
25
+ --color-danger-hover: #dc2626;
26
+ --color-danger-light: #fca5a5;
27
+
28
+ --color-warning: #f59e0b;
29
+ --color-warning-hover: #d97706;
30
+ --color-warning-light: #fcd34d;
31
+
32
+ /* Neutral Colors - Light Mode */
33
+ --color-white: #ffffff;
34
+ --color-gray-50: #f9fafb;
35
+ --color-gray-100: #f3f4f6;
36
+ --color-gray-200: #e5e7eb;
37
+ --color-gray-300: #d1d5db;
38
+ --color-gray-400: #9ca3af;
39
+ --color-gray-500: #6b7280;
40
+ --color-gray-600: #4b5563;
41
+ --color-gray-700: #374151;
42
+ --color-gray-800: #1f2937;
43
+ --color-gray-900: #111827;
44
+ --color-black: #000000;
45
+
46
+ /* Typography Scale */
47
+ --font-size-xs: 0.75rem;
48
+ --font-size-sm: 0.875rem;
49
+ --font-size-base: 1rem;
50
+ --font-size-lg: 1.125rem;
51
+ --font-size-xl: 1.25rem;
52
+ --font-size-2xl: 1.5rem;
53
+ --font-size-3xl: 1.875rem;
54
+ --font-size-4xl: 2.25rem;
55
+ --font-montserrat: "Montserrat", "Inter", system-ui, sans-serif;
56
+
57
+ /* Line Heights */
58
+ --line-height-tight: 1.25;
59
+ --line-height-normal: 1.5;
60
+ --line-height-relaxed: 1.75;
61
+
62
+ /* Spacing Scale */
63
+ --spacing-xs: 0.25rem;
64
+ --spacing-sm: 0.5rem;
65
+ --spacing-md: 1rem;
66
+ --spacing-lg: 1.5rem;
67
+ --spacing-xl: 2rem;
68
+ --spacing-2xl: 3rem;
69
+ --spacing-3xl: 4rem;
70
+
71
+ /* Border Radius */
72
+ --radius-sm: 0.25rem;
73
+ --radius-md: 0.375rem;
74
+ --radius-lg: 0.5rem;
75
+ --radius-xl: 0.75rem;
76
+ --radius-2xl: 1rem;
77
+
78
+ /* Shadows */
79
+ --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
80
+ --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
81
+ --shadow-lg:
82
+ 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
83
+ --shadow-xl:
84
+ 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
85
+ }
86
+
87
+ @custom-variant dark (&:where([data-theme="dark"], [data-theme="dark"] *));
88
+
89
+ :root {
90
+ /* Dark Mode Theme Variables */
91
+ --theme-bg-primary: #121212; /* Main background */
92
+ --theme-bg-secondary: #171717; /* Secondary background */
93
+ --theme-bg-tertiary: #222222; /* Tertiary background */
94
+ --theme-text-primary: #f8fafc; /* Primary text */
95
+ --theme-text-secondary: #cbd5e1; /* Secondary text */
96
+ --theme-text-tertiary: #94a3b8; /* Tertiary text */
97
+ --theme-border-primary: #1f2937; /* Borders */
98
+ --theme-border-secondary: #f59e0b; /* Accent borders / focus */
99
+
100
+ /* Component Colors */
101
+ --theme-card-bg: #171717; /* Card background */
102
+ --theme-card-border: #1f2937; /* Card borders */
103
+ --theme-input-bg: #141414; /* Input background */
104
+ --theme-input-border: #334155; /* Input borders */
105
+ --theme-input-border-focus: #f59e0b; /* Input focus borders */
106
+ --theme-button-bg: #f97316; /* Button background */
107
+ --theme-button-hover: #ea580c; /* Button hover */
108
+ }
109
+
110
+ [data-theme="light"] {
111
+ /* Light Mode Theme Variables */
112
+ --theme-bg-primary: var(--color-white);
113
+ --theme-bg-secondary: var(--color-gray-50);
114
+ --theme-bg-tertiary: var(--color-gray-100);
115
+ --theme-text-primary: var(--color-gray-900);
116
+ --theme-text-secondary: var(--color-gray-600);
117
+ --theme-text-tertiary: var(--color-gray-500);
118
+ --theme-border-primary: var(--color-gray-200);
119
+ --theme-border-secondary: var(--color-gray-300);
120
+
121
+ /* Component Colors */
122
+ --theme-card-bg: var(--color-white);
123
+ --theme-card-border: var(--color-gray-200);
124
+ --theme-input-bg: var(--color-white);
125
+ --theme-input-border: var(--color-gray-300);
126
+ --theme-input-border-focus: var(--color-gray-400);
127
+ --theme-button-bg: var(--color-primary);
128
+ --theme-button-hover: var(--color-primary-hover);
129
+ }
130
+
131
+ /* View Transitions API - Navigation Animations */
132
+ @media (prefers-reduced-motion: no-preference) {
133
+ ::view-transition-old(root) {
134
+ animation: 150ms ease-out both fade-out;
135
+ }
136
+
137
+ ::view-transition-new(root) {
138
+ animation: 150ms ease-in 50ms both fade-in;
139
+ }
140
+
141
+ @keyframes fade-out {
142
+ from {
143
+ opacity: 1;
144
+ }
145
+ to {
146
+ opacity: 0;
147
+ }
148
+ }
149
+
150
+ @keyframes fade-in {
151
+ from {
152
+ opacity: 0;
153
+ }
154
+ to {
155
+ opacity: 1;
156
+ }
157
+ }
158
+ }
159
+
160
+ /* Disable view transitions for users who prefer reduced motion */
161
+ @media (prefers-reduced-motion: reduce) {
162
+ ::view-transition-group(*),
163
+ ::view-transition-old(*),
164
+ ::view-transition-new(*) {
165
+ animation: none !important;
166
+ }
167
+ }
168
+
169
+ @layer base {
170
+ /* Enable smooth transitions only during theme toggles. */
171
+ :root.theme-transition * {
172
+ transition:
173
+ background-color 0.2s ease-in-out,
174
+ color 0.2s ease-in-out,
175
+ border-color 0.2s ease-in-out;
176
+ }
177
+
178
+ @media (prefers-reduced-motion: reduce) {
179
+ :root.theme-transition * {
180
+ transition: none !important;
181
+ }
182
+ }
183
+
184
+ /* Only remove focus styles for non-keyboard interactions */
185
+ *:focus:not(:focus-visible) {
186
+ outline: none;
187
+ box-shadow: none;
188
+ }
189
+
190
+ /* Ensure keyboard focus is always visible */
191
+ *:focus-visible {
192
+ outline: 2px solid var(--color-primary);
193
+ outline-offset: 2px;
194
+ }
195
+
196
+ body {
197
+ background-color: var(--theme-bg-primary);
198
+ color: var(--theme-text-primary);
199
+ font-family: Inter, system-ui, sans-serif;
200
+ font-size: var(--font-size-base);
201
+ line-height: var(--line-height-normal);
202
+ }
203
+
204
+ button:not(:disabled),
205
+ [role="button"]:not([aria-disabled="true"]),
206
+ a[href],
207
+ summary,
208
+ select,
209
+ label[for],
210
+ input[type="checkbox"],
211
+ input[type="radio"],
212
+ input[type="range"],
213
+ input[type="date"],
214
+ input[type="time"],
215
+ input[type="datetime-local"],
216
+ input[type="month"],
217
+ input[type="week"],
218
+ input[type="color"],
219
+ input[type="file"],
220
+ input[type="submit"],
221
+ input[type="button"],
222
+ input[type="reset"] {
223
+ cursor: pointer;
224
+ }
225
+
226
+ input:not([type]),
227
+ input[type="text"],
228
+ input[type="email"],
229
+ input[type="password"],
230
+ input[type="search"],
231
+ input[type="tel"],
232
+ input[type="url"],
233
+ input[type="number"],
234
+ textarea {
235
+ cursor: text;
236
+ }
237
+
238
+ button:disabled,
239
+ input:disabled,
240
+ select:disabled,
241
+ textarea:disabled,
242
+ [aria-disabled="true"] {
243
+ cursor: not-allowed;
244
+ }
245
+ }
246
+
247
+ /* Nivo tooltip theming */
248
+ div[style*="pointer-events: none;"] > div[style*="background: white;"],
249
+ div[style*="pointer-events: none;"]
250
+ > div[style*="background: rgb(255, 255, 255)"] {
251
+ background: var(--theme-card-bg) !important;
252
+ color: var(--theme-text-primary) !important;
253
+ border: 1px solid var(--theme-border-primary) !important;
254
+ border-radius: 8px !important;
255
+ }
256
+
257
+ div[style*="pointer-events: none;"]
258
+ > div[style*="background: white;"]
259
+ table
260
+ tbody
261
+ tr
262
+ td,
263
+ div[style*="pointer-events: none;"]
264
+ > div[style*="background: rgb(255, 255, 255)"]
265
+ table
266
+ tbody
267
+ tr
268
+ td {
269
+ color: var(--theme-text-primary) !important;
270
+ }
271
+
272
+ div[style*="pointer-events: none;"]
273
+ > div[style*="background: white;"]
274
+ table
275
+ tbody
276
+ tr
277
+ td[style*="color: rgb(0, 0, 0);"],
278
+ div[style*="pointer-events: none;"]
279
+ > div[style*="background: rgb(255, 255, 255)"]
280
+ table
281
+ tbody
282
+ tr
283
+ td[style*="color: rgb(0, 0, 0);"] {
284
+ color: var(--theme-text-primary) !important;
285
+ }
@@ -0,0 +1,69 @@
1
+ import { env } from "cloudflare:workers";
2
+
3
+ const TRUE_VALUES = new Set(["1", "true", "yes", "on"]);
4
+
5
+ const isEnabled = (value: unknown) => {
6
+ if (typeof value === "boolean") return value;
7
+ if (typeof value !== "string") return false;
8
+ return TRUE_VALUES.has(value.trim().toLowerCase());
9
+ };
10
+
11
+ const readRawFlag = (keys: string[]) => {
12
+ const bindingValues = env as unknown as Record<string, string | boolean | undefined>;
13
+ for (const key of keys) {
14
+ if (bindingValues[key] !== undefined) return bindingValues[key];
15
+ }
16
+
17
+ const processEnv =
18
+ typeof process !== "undefined"
19
+ ? (process as { env?: Record<string, string | undefined> }).env
20
+ : undefined;
21
+
22
+ for (const key of keys) {
23
+ if (processEnv?.[key] !== undefined) return processEnv[key];
24
+ }
25
+
26
+ return undefined;
27
+ };
28
+
29
+ const resolveFlag = (keys: string[], defaultValue: boolean) => {
30
+ const raw = readRawFlag(keys);
31
+
32
+ if (raw === undefined || raw === null) {
33
+ return defaultValue;
34
+ }
35
+
36
+ if (typeof raw === "string" && raw.trim().length === 0) {
37
+ return defaultValue;
38
+ }
39
+
40
+ return isEnabled(raw);
41
+ };
42
+
43
+ export const isReportBuilderEnabled = () => {
44
+ return resolveFlag(["REPORT_BUILDER"], false);
45
+ };
46
+
47
+ export const isAskAiEnabled = () => {
48
+ return resolveFlag(["ASK_AI", "ASK_AI_ENABLED"], true);
49
+ };
50
+
51
+ export const isDashboardEnabled = () => {
52
+ return resolveFlag(["LYTX_FEATURE_DASHBOARD", "LYTX_DASHBOARD"], true);
53
+ };
54
+
55
+ export const isEventsEnabled = () => {
56
+ return resolveFlag(["LYTX_FEATURE_EVENTS", "LYTX_EVENTS_ENABLED"], true);
57
+ };
58
+
59
+ export const isAuthEnabled = () => {
60
+ return resolveFlag(["LYTX_FEATURE_AUTH", "LYTX_AUTH_ENABLED"], true);
61
+ };
62
+
63
+ export const isAiFeatureEnabled = () => {
64
+ return resolveFlag(["LYTX_FEATURE_AI", "LYTX_AI_ENABLED"], true);
65
+ };
66
+
67
+ export const isTagScriptEnabled = () => {
68
+ return resolveFlag(["LYTX_FEATURE_TAG_SCRIPT", "LYTX_TAG_SCRIPT_ENABLED"], true);
69
+ };