diff --git a/frontend/src/main.ts b/frontend/src/main.ts index fcc1ceb..de5ddc8 100644 --- a/frontend/src/main.ts +++ b/frontend/src/main.ts @@ -68,6 +68,24 @@ import { type UserVaultPosition, } from "./defindex.ts"; +type PlausibleOptions = { + props?: Record; + interactive?: boolean; +}; + +type PlausibleFn = ((eventName: string, options?: PlausibleOptions) => void) & { q?: unknown[] }; + +declare global { + interface Window { + plausible?: PlausibleFn; + doNotTrack?: string; + } + + interface Navigator { + msDoNotTrack?: string; + } +} + // ── Wallet kit ──────────────────────────────────────────────────────────────── StellarWalletsKit.init({ @@ -335,6 +353,47 @@ const fmt = (n: number, d = 2) => n.toLocaleString("en-US", { maximumFractionDigits: d, minimumFractionDigits: d }); const aprToApy = (apr: number) => (Math.exp(apr / 100) - 1) * 100; const fmtAddr = (addr: string) => addr.slice(0, 6) + "…" + addr.slice(-4); +const ANALYTICS_DOMAIN = "turbolong.com"; + +function doNotTrackEnabled(): boolean { + return navigator.doNotTrack === "1" || navigator.msDoNotTrack === "1" || window.doNotTrack === "1"; +} + +function ensurePlausibleQueue() { + if (window.plausible) return; + const queued = ((eventName: string, options?: PlausibleOptions) => { + queued.q = queued.q || []; + queued.q.push([eventName, options]); + }) as PlausibleFn; + window.plausible = queued; +} + +function initPrivacyAnalytics() { + if (doNotTrackEnabled()) return; + ensurePlausibleQueue(); + if (!document.querySelector('script[data-turbolong-analytics="plausible"]')) { + const script = document.createElement("script"); + script.defer = true; + script.src = "https://plausible.io/js/script.manual.js"; + script.dataset.domain = ANALYTICS_DOMAIN; + script.dataset.turbolongAnalytics = "plausible"; + document.head.appendChild(script); + } + trackFunnelEvent("App Loaded", { surface: "app" }, false); +} + +function trackFunnelEvent(eventName: string, props: Record = {}, interactive = true) { + if (doNotTrackEnabled()) return; + ensurePlausibleQueue(); + window.plausible?.(eventName, { + interactive, + props: { + surface: "app", + network: getActiveNetwork(), + ...props, + }, + }); +} // ── Skeleton loading (#3) ──────────────────────────────────────────────────── @@ -1378,6 +1437,12 @@ async function openPosition() { await signAndSubmit(submitXdr, `Open ${liveAsset.symbol} leverage`, 1); hideTxStepper(); savePnlEntry(liveAsset.id, selectedPool.id, initial); + trackFunnelEvent("Deposit Submitted", { + flow: "leverage", + pool: selectedPool.name, + asset: liveAsset.symbol, + leverage, + }); await loadAll(); } catch (e: any) { markStepperError(2); @@ -1585,6 +1650,12 @@ async function addFundsToPosition() { const newDeposit = (existingPnl?.deposit ?? 0) + additional; savePnlEntry(liveAsset.id, selectedPool.id, newDeposit); ($("add-funds-input") as HTMLInputElement).value = ""; + trackFunnelEvent("Deposit Submitted", { + flow: "add_funds", + pool: selectedPool.name, + asset: liveAsset.symbol, + leverage, + }); await loadAll(); } catch (e: any) { markStepperError(2); @@ -1721,6 +1792,7 @@ async function connect() { buildAssetTabs(); renderPoolFooter(); await loadAll(); + trackFunnelEvent("Connect Wallet", { flow: "wallet" }); } catch (e: any) { if (e?.message !== "User closed the modal") toast("Failed to connect wallet", "error"); } @@ -2260,6 +2332,7 @@ updatePreview(); renderTxHistory(); renderPoolFooter(); initTooltips(); +initPrivacyAnalytics(); // ── Overview (cross-protocol dashboard) ─────────────────────────────────────── @@ -2649,6 +2722,11 @@ $("vault-deposit-btn").addEventListener("click", async () => { const xdr = await buildVaultDepositXdr(vault, userAddress, amount); const { signedTxXdr } = await StellarWalletsKit.signTransaction(xdr); await submitSignedXdr(signedTxXdr); + trackFunnelEvent("Deposit Submitted", { + flow: "vault", + pool: "DeFindex", + asset: vault.assetSymbol, + }); await refreshVaultView(); ($("vault-deposit-input") as HTMLInputElement).value = ""; } catch (err: any) { diff --git a/landing/index.html b/landing/index.html index fc63e75..2416438 100644 --- a/landing/index.html +++ b/landing/index.html @@ -33,7 +33,7 @@ How It Works Features - Launch App + Launch App @@ -42,8 +42,8 @@

Leverage trading on Stellar. Simplified.

Open leveraged long positions on Blend Protocol pools with a single click. No perps, no funding rates — just recursive lending loops.

@@ -137,7 +137,7 @@

Built on Stellar

Start leveraging on Stellar

Connect your Stellar wallet and open your first position in minutes.

- Launch App + Launch App
@@ -146,13 +146,44 @@

Start leveraging on Stellar

Blend Docs GitHub Stellar Expert + Privacy - - + diff --git a/landing/privacy.html b/landing/privacy.html new file mode 100644 index 0000000..e25e3f1 --- /dev/null +++ b/landing/privacy.html @@ -0,0 +1,46 @@ + + + + + + Turbolong Privacy + + + + + + + + +
+ + +
+

Privacy

+
+
+

Cookie-free analytics

+

Turbolong uses Plausible-style custom events for aggregate funnel metrics only. The integration does not set tracking cookies and does not create cross-site advertising profiles.

+
+
+

No wallet PII

+

Analytics events never include wallet addresses, email addresses, transaction hashes, exact deposit amounts, IP-derived identities, or free-form user input.

+
+
+

Tracked funnel events

+

The landing page tracks page view and launch/demo clicks. The app tracks app load, successful wallet connection, and submitted deposit flows using coarse properties such as network, pool name, asset symbol, leverage, and flow type.

+
+
+

Do Not Track

+

If your browser sends Do Not Track, Turbolong does not load the analytics script and does not queue analytics events.

+
+
+
+
+ +