Write in JavaScript + JSX
Compose familiar declarative layouts and use versioned APIs for data, files, networking, storage, device state, and more.
diff --git a/docs/cross-promotion-measurement.md b/docs/cross-promotion-measurement.md new file mode 100644 index 0000000..c277a9a --- /dev/null +++ b/docs/cross-promotion-measurement.md @@ -0,0 +1,34 @@ +# Cross-promotion measurement + +ScriptWidget's landing page accepts these attribution parameters: + +- `utm_source` +- `utm_medium` +- `utm_campaign` +- `utm_content` + +The page keeps the values for the current browser session and emits these events through +`window.dataLayer`, `window.plausible` (when present), and the `xnu:analytics` custom event: + +- `landing_view` +- `app_store_click` +- `github_click` +- `tutorial_click` + +The initial Remoboard placement uses: + +```text +utm_source=remoboard +utm_medium=in_app +utm_campaign=cross_promo +utm_content=home_featured_card +``` + +Use `landing_view` as the number of visits from Remoboard and `app_store_click` as the +number of App Store handoffs. Report the click-through rate as App Store handoffs divided +by landing visits. + +To attribute first-time downloads, create an App Store Connect campaign named +`remoboard_cross_promo`, then add the generated `pt`, `ct`, and `mt` parameters to the +App Store URL in `site/index.html`. Apple requires a provider token generated by App Store +Connect; it must not be guessed or created manually. diff --git a/site/analytics.js b/site/analytics.js new file mode 100644 index 0000000..a4e65a4 --- /dev/null +++ b/site/analytics.js @@ -0,0 +1,54 @@ +(function () { + "use strict"; + + var allowedParameters = ["utm_source", "utm_medium", "utm_campaign", "utm_content"]; + var query = new URLSearchParams(window.location.search); + var attribution = {}; + + allowedParameters.forEach(function (name) { + var value = query.get(name); + if (value) { + attribution[name] = value.slice(0, 100); + } + }); + + if (Object.keys(attribution).length > 0) { + try { + window.sessionStorage.setItem("scriptwidget_attribution", JSON.stringify(attribution)); + } catch (_) { + // Attribution remains available for this page view when storage is unavailable. + } + } else { + try { + attribution = JSON.parse(window.sessionStorage.getItem("scriptwidget_attribution")) || {}; + } catch (_) { + attribution = {}; + } + } + + function track(name, properties) { + var eventProperties = Object.assign({}, attribution, properties || {}); + window.dataLayer = window.dataLayer || []; + window.dataLayer.push({ event: name, properties: eventProperties }); + + if (typeof window.plausible === "function") { + window.plausible(name, { props: eventProperties }); + } + + window.dispatchEvent(new CustomEvent("xnu:analytics", { + detail: { name: name, properties: eventProperties } + })); + } + + window.xnuTrack = track; + track("landing_view", { page: "scriptwidget" }); + + document.querySelectorAll("[data-track-event]").forEach(function (link) { + link.addEventListener("click", function () { + track(link.dataset.trackEvent, { + label: link.dataset.trackLabel || "", + destination: link.hostname + }); + }); + }); +}()); diff --git a/site/index.html b/site/index.html index 7bfe9ee..6ef517a 100644 --- a/site/index.html +++ b/site/index.html @@ -1,9 +1,9 @@
Write JavaScript and JSX, preview in ScriptWidget Studio, and render native WidgetKit experiences across Apple platforms—without authoring every widget in Swift.

Write JavaScript and JSX, preview in ScriptWidget Studio, and render native WidgetKit experiences across Apple platforms—without authoring every widget in Swift.

Compose familiar declarative layouts and use versioned APIs for data, files, networking, storage, device state, and more.
Edit with diagnostics, inspect runtime output, and preview multiple widget families without repeating an Xcode build loop.
Use versioned manifests, explicit permissions, host allowlists, and the community Gallery to distribute reusable widgets.
$render(
<vstack frame="max" padding="16" background="#171923">
<text font="caption" color="#a9b0c0">Today</text>
<text font="largeTitle" color="#ffffff">Build something useful.</text>
</vstack>
);