// PayToLaugh front end. // // NOTE ON THE TOKEN BELOW: it is public by construction. A static page cannot // keep a secret, so the endpoint behind it is built to be safe in public // hands. It reads and writes small records only and cannot spend money. // Script and video generation are deliberately NOT reachable from here. // // NOTE ON "LOGIN": an email address identifies you, it does not authenticate // you. Anyone who knows an address can see that account. Temporary, by // agreement, until real auth lands. var API = "https://api.corben.world/functions/da0474ea-4e56-405c-9f0d-35c3e4037a69/invoke" + "?token=97cd624cd2bd6a74eb692a940e71851f"; var PHOTO_KINDS = ["head shot", "profile", "full body", "sitting", "playing", "sleeping", "action", "other"]; // A plate is built from a head-on shot; these are the ones worth having. var REFERENCE_KINDS = ["head shot", "profile", "full body"]; async function api(action, payload) { var body = Object.assign({ action: action }, payload || {}); var res; try { res = await fetch(API, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) }); } catch (e) { return { ok: false, error: "Could not reach the server. Check your connection and try again." }; } var outer; try { outer = await res.json(); } catch (e) { return { ok: false, error: "The server sent something unreadable." }; } return outer && outer.result ? outer.result : outer; } var Session = { key: "ptl_email", nameKey: "ptl_name", get: function () { try { return localStorage.getItem(this.key); } catch (e) { return null; } }, name: function () { try { return localStorage.getItem(this.nameKey) || ""; } catch (e) { return ""; } }, set: function (email, name) { try { localStorage.setItem(this.key, email); if (name) localStorage.setItem(this.nameKey, name); } catch (e) {} }, clear: function () { try { localStorage.removeItem(this.key); localStorage.removeItem(this.nameKey); } catch (e) {} } }; function esc(s) { return String(s == null ? "" : s).replace(/[&<>\"']/g, function (c) { return { "&": "&", "<": "<", ">": ">", '\"': """, "'": "'" }[c]; }); } function navBar(current) { var email = Session.get(); var links = [["/", "Home", "home"], ["/pets.html", "My pets", "pets"], ["/create.html", "New video", "create"], ["/videos.html", "My videos", "videos"]]; var html = ''; document.body.insertAdjacentHTML("afterbegin", html); var b = document.getElementById("ptl-signout"); if (b) b.onclick = function () { Session.clear(); location.href = "/register.html"; }; } function requireSession() { var e = Session.get(); if (!e) { location.href = "/register.html?next=" + encodeURIComponent(location.pathname); return null; } return e; } function showMsg(id, text, kind) { var el = document.getElementById(id); if (!el) return; el.className = "msg " + (kind || "err"); el.textContent = text; } function hideMsg(id) { var el = document.getElementById(id); if (el) el.className = "msg"; } // Mirrors the server's arithmetic exactly: $0.40 per 8s clip plus $0.17 for // script and plate, at a 5x markup. If one changes, change both. function priceFor(segments) { var cost = segments * 0.40 + 0.17; return { cost: cost, price: Math.round(cost * 5 * 100) / 100, seconds: segments * 8 }; } // Shrink in the browser before upload. Phone photos run to 5-10MB, which is // slow to send and far more detail than a reference plate needs; 1600px is // ample. Returns base64 without the data: prefix. function resizeImage(file, maxPx, quality) { maxPx = maxPx || 1600; quality = quality || 0.85; return new Promise(function (resolve, reject) { var reader = new FileReader(); reader.onerror = function () { reject(new Error("Could not read that file.")); }; reader.onload = function () { var img = new Image(); img.onerror = function () { reject(new Error("That file is not an image we can read.")); }; img.onload = function () { var w = img.naturalWidth, h = img.naturalHeight; if (!w || !h) { reject(new Error("That image looks empty.")); return; } var scale = Math.min(1, maxPx / Math.max(w, h)); var cw = Math.round(w * scale), ch = Math.round(h * scale); var c = document.createElement("canvas"); c.width = cw; c.height = ch; var g = c.getContext("2d"); g.fillStyle = "#ffffff"; g.fillRect(0, 0, cw, ch); // flatten transparency for JPEG g.drawImage(img, 0, 0, cw, ch); var url; try { url = c.toDataURL("image/jpeg", quality); } catch (e) { reject(new Error("That image could not be processed.")); return; } resolve({ b64: url.split(",")[1], width: cw, height: ch }); }; img.src = reader.result; }; reader.readAsDataURL(file); }); }