diff --git a/static/app.js b/static/app.js
index dd32f2c..6e83f37 100644
--- a/static/app.js
+++ b/static/app.js
@@ -8,6 +8,7 @@ let clockOffset = 0; // serverTime - clientTime
let token = localStorage.getItem(TOKEN_KEY);
let statsLoaded = false;
let subscribed = false;
+let currentShareText = "";
async function api(method, path, body) {
const headers = { "Content-Type": "application/json" };
@@ -36,6 +37,45 @@ function formatPrecise(ms) {
return `${formatMs(ms).slice(0, -3)}.${String(Math.max(0, ms) % 1000).padStart(3, "0")}`;
}
+function updateShareLinks() {
+ if (!currentShareText) return;
+ const gameUrl = new URL(location.pathname, location.origin).href;
+ const facebookUrl = new URL("https://www.facebook.com/sharer/sharer.php");
+ facebookUrl.searchParams.set("u", gameUrl);
+ facebookUrl.searchParams.set("quote", currentShareText);
+ $("share-facebook").href = facebookUrl.href;
+
+ const xUrl = new URL("https://twitter.com/intent/tweet");
+ xUrl.searchParams.set("text", currentShareText);
+ xUrl.searchParams.set("url", gameUrl);
+ $("share-x").href = xUrl.href;
+
+ const blueskyUrl = new URL("https://bsky.app/intent/compose");
+ blueskyUrl.searchParams.set("text", `${currentShareText} ${gameUrl}`);
+ $("share-bluesky").href = blueskyUrl.href;
+
+ const instanceInput = $("mastodon-instance");
+ const mastodonLink = $("share-mastodon");
+ const shareError = $("share-error");
+ try {
+ const instance = new URL(instanceInput.value.trim());
+ if (instance.protocol !== "https:" || !instance.hostname || instance.username || instance.password) {
+ throw new Error("Enter an HTTPS Mastodon instance URL.");
+ }
+ const mastodonUrl = new URL("/share", instance.origin);
+ mastodonUrl.searchParams.set("text", `${currentShareText} ${gameUrl}`);
+ mastodonLink.href = mastodonUrl.href;
+ mastodonLink.removeAttribute("aria-disabled");
+ mastodonLink.tabIndex = 0;
+ shareError.textContent = "";
+ } catch {
+ mastodonLink.removeAttribute("href");
+ mastodonLink.setAttribute("aria-disabled", "true");
+ mastodonLink.tabIndex = -1;
+ shareError.textContent = "Enter an HTTPS Mastodon instance URL to enable sharing.";
+ }
+}
+
function zoneFor(remaining) {
return game.zones.find((z) => remaining > z.remaining_to_ms) ?? game.zones[game.zones.length - 1];
}
@@ -200,11 +240,16 @@ async function loadStats() {
$("the-button").addEventListener("click", async () => {
const btn = $("the-button");
btn.disabled = true;
+ $("share-controls").hidden = true;
+ currentShareText = "";
try {
const r = await api("POST", "/api/press");
game.deadline = r.deadline;
+ currentShareText = `I pushed the button at ${formatPrecise(r.press.remaining_ms)}.`;
$("press-result").textContent =
`You pressed in the ${r.press.zone} zone with ${formatPrecise(r.press.remaining_ms)} remaining: +${r.press.points} points`;
+ updateShareLinks();
+ $("share-controls").hidden = false;
await loadMe();
} catch (e) {
$("press-result").textContent = e.message;
@@ -214,6 +259,8 @@ $("the-button").addEventListener("click", async () => {
}
});
+$("mastodon-instance").addEventListener("input", updateShareLinks);
+
$("register-form").addEventListener("submit", async (ev) => {
ev.preventDefault();
$("register-error").textContent = "";
diff --git a/static/index.html b/static/index.html
index c03cb8e..610de40 100644
--- a/static/index.html
+++ b/static/index.html
@@ -27,6 +27,20 @@
diff --git a/static/style.css b/static/style.css
index 63b5cf4..1a64957 100644
--- a/static/style.css
+++ b/static/style.css
@@ -102,6 +102,29 @@ input, form button {
padding: 0.4rem 0.8rem;
}
+.share-links {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+ align-items: center;
+ gap: 0.5rem;
+ margin: 0.5rem 0 1rem;
+}
+
+.share-link {
+ display: inline-block;
+ padding: 0.45rem 0.8rem;
+ border: 1px solid #bbb;
+ border-radius: 4px;
+ color: #fff;
+ text-decoration: none;
+}
+
+.share-link:hover, .share-link:focus-visible { background: #666; }
+.share-link[aria-disabled="true"] { opacity: 0.5; pointer-events: none; }
+#mastodon-instance { max-width: 100%; }
+#share-controls label { display: block; }
+
.error { color: var(--red); }
#rules { text-align: left; font-size: 0.9rem; color: #ccc; }