diff --git a/assets/app.css b/assets/app.css new file mode 100644 index 0000000..21b4f3c --- /dev/null +++ b/assets/app.css @@ -0,0 +1,77 @@ +:root{ + --bg:#0e0f13; --panel:#16181f; --line:#262a35; + --ink:#e9ecf2; --muted:#9aa3b2; --accent:#4B8BF5; --accent2:#f5c04b; + --bad:#f5714b; --good:#4bf58b; + --mono:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; +} +*{box-sizing:border-box} +body{margin:0;background:var(--bg);color:var(--ink); + font:16px/1.65 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;} +.wrap{max-width:940px;margin:0 auto;padding:0 22px} +a{color:var(--accent)} + +nav{border-bottom:1px solid var(--line);padding:18px 0;margin-bottom:8px} +nav .inner{max-width:940px;margin:0 auto;padding:0 22px;display:flex;gap:22px;align-items:center;flex-wrap:wrap} +nav .brand{font-weight:700;letter-spacing:-.01em;color:var(--ink);text-decoration:none} +nav .brand span{color:var(--accent)} +nav a.link{color:var(--muted);text-decoration:none;font-size:14.5px} +nav a.link:hover,nav a.link.on{color:var(--ink)} +nav .who{margin-left:auto;font:12.5px/1 var(--mono);color:var(--muted);display:flex;gap:12px;align-items:center} +nav .who button{background:none;border:0;color:var(--muted);cursor:pointer;font:inherit;text-decoration:underline} + +header.page{padding:46px 0 30px} +header.page h1{font-size:clamp(28px,5vw,42px);line-height:1.08;margin:0 0 12px;letter-spacing:-.02em} +header.page p{color:var(--muted);max-width:62ch;margin:0;font-size:17px} +section{padding:34px 0} +h2.sec{font-size:13px;font-family:var(--mono);letter-spacing:.16em;text-transform:uppercase; + color:var(--muted);margin:0 0 20px;font-weight:600} + +.card{background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:26px} +.field{margin-bottom:20px} +.field label{display:block;font-size:14px;font-weight:600;margin-bottom:6px} +.field .hint{display:block;color:var(--muted);font-size:13.5px;margin-bottom:9px;font-weight:400} +input[type=text],input[type=email],input[type=url],textarea,select{ + width:100%;background:#0f1117;border:1px solid var(--line);border-radius:9px; + color:var(--ink);padding:11px 13px;font:15px/1.5 inherit} +input:focus,textarea:focus,select:focus{outline:none;border-color:var(--accent)} +textarea{min-height:110px;resize:vertical} +.row{display:grid;grid-template-columns:1fr 1fr;gap:16px} +@media(max-width:640px){.row{grid-template-columns:1fr}} + +.btn{background:var(--accent);color:#fff;border:0;border-radius:9px;padding:12px 22px; + font:600 15px/1 inherit;cursor:pointer} +.btn:hover{filter:brightness(1.08)} +.btn[disabled]{opacity:.5;cursor:default} +.btn.ghost{background:none;border:1px solid var(--line);color:var(--ink)} + +.msg{border-radius:9px;padding:12px 15px;margin-bottom:18px;font-size:14.5px;display:none} +.msg.err{display:block;background:#2a1512;border:1px solid #5b2a20;color:#ffb9a7} +.msg.ok{display:block;background:#10241a;border:1px solid #1f5138;color:#a7f0c8} + +.chips{display:flex;gap:10px;flex-wrap:wrap} +.chip{border:1px solid var(--line);border-radius:999px;padding:8px 15px;cursor:pointer;font-size:14px;color:var(--muted);background:#0f1117} +.chip.on{border-color:var(--accent);color:var(--ink);background:#141a28} + +.quote{border:1px solid var(--line);border-radius:10px;padding:15px 18px;background:#0f1117; + display:flex;justify-content:space-between;align-items:baseline;gap:14px;flex-wrap:wrap} +.quote .big{font-size:26px;font-weight:700;letter-spacing:-.01em} +.quote .sub{color:var(--muted);font-size:13.5px;font-family:var(--mono)} + +.list{display:grid;gap:16px} +.item{background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:20px; + display:grid;grid-template-columns:150px 1fr;gap:22px} +@media(max-width:640px){.item{grid-template-columns:1fr}} +.item video{width:100%;aspect-ratio:9/16;border-radius:10px;background:#000;border:1px solid var(--line)} +.item .ph{width:100%;aspect-ratio:9/16;border-radius:10px;background:#0f1117;border:1px dashed var(--line); + display:flex;align-items:center;justify-content:center;color:var(--muted);font-size:12px;text-align:center;padding:12px} +.item h3{margin:0 0 6px;font-size:18px} +.item .meta{color:var(--muted);font-size:13.5px;margin:0 0 10px;word-break:break-word} +.badge{display:inline-block;font:600 11px/1 var(--mono);letter-spacing:.1em;text-transform:uppercase; + padding:5px 9px;border-radius:5px;margin-bottom:10px} +.badge.submitted{background:#1d1a0d;color:var(--accent2);border:1px solid #3a3212} +.badge.ready{background:#10241a;color:#7fe3a8;border:1px solid #1f5138} +.badge.failed{background:#2a1512;color:#ffb9a7;border:1px solid #5b2a20} + +.empty{border:1px dashed var(--line);border-radius:14px;padding:44px 26px;text-align:center;color:var(--muted)} +.note{background:#1a1410;border:1px solid #3d2f1c;border-radius:12px;padding:16px 19px;color:#d9c9ae;font-size:14.5px} +footer{padding:44px 0 70px;color:var(--muted);font-size:13.5px;border-top:1px solid var(--line);margin-top:40px} diff --git a/assets/app.js b/assets/app.js new file mode 100644 index 0000000..3d1f8e1 --- /dev/null +++ b/assets/app.js @@ -0,0 +1,97 @@ +// 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"; + +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." }; } + // The invoke route wraps the function result. + 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"], ["/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"; }; +} + +// Pages that need an identity send you to sign in rather than failing oddly. +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 }; +} diff --git a/create.html b/create.html new file mode 100644 index 0000000..dda07bc --- /dev/null +++ b/create.html @@ -0,0 +1,191 @@ + + + + + +New video — PayToLaugh + + + + +
+ +
+

Make a new video

+

Pick the pet, paste whatever you want them to react to, and tell the director what you actually think. The opinion is yours; the delivery is theirs.

+
+ +
+
+
+
+ +

1 · Who is talking

+
+ + +
+ +
+
+
+ + +
+
+ + +
+
+
+ + Colour and markings matter more than you would think. Naming them plainly is what keeps the likeness. + +
+
+ + This is what makes it sound like your pet and not a generic one. + +
+
+ +

2 · What they are reacting to

+
+ + Anything with a claim in it. News piece, a thread, a video. + +
+
+ + Say what you think and how you want it played. Two sentences is plenty. + +
+
+ +
+ Let the writer decide + Agree + Disagree + A bit of both +
+
+ +

3 · How long

+
+
+ 16 seconds + 32 seconds + 48 seconds +
+
+
+
$8.85
32 seconds · 4 shots
+
You approve the script before anything is filmed.
+
+ + +
+
+ +

+ What happens next. Submitting records your request and puts it in the + queue. Nothing is charged and nothing is filmed yet. Payment and automatic + production get switched on once proper sign-in is in place. +

+
+ + +
+ + + + + diff --git a/register.html b/register.html new file mode 100644 index 0000000..094e890 --- /dev/null +++ b/register.html @@ -0,0 +1,70 @@ + + + + + +Sign in — PayToLaugh + + + + +
+ +
+

Sign in

+

Your email is all we need for now. If you have not been here before, this creates your account.

+
+ +
+
+
+
+
+ + We use this to keep your pets and videos together. + +
+
+ + +
+ +
+
+ +

+ Early access. There is no password yet, so an email address identifies + you rather than proves who you are. Do not put anything here you would mind + another person seeing. Proper sign-in is coming. +

+
+ + +
+ + + + + diff --git a/videos.html b/videos.html new file mode 100644 index 0000000..8cec124 --- /dev/null +++ b/videos.html @@ -0,0 +1,86 @@ + + + + + +My videos — PayToLaugh + + + + +
+ +
+

My videos

+

Everything you have submitted, newest first.

+
+ +
+
+

Loading…

+
+ + +
+ + + + +