MediaWiki:Common.js: Difference between revisions
Jump to navigation
Jump to search
m PIN gate v3: render unlocked trip in iframe srcdoc (fixes HTML source view) |
m Beautify PIN unlock viewer (homecoming styles + hero) |
||
| Line 13: | Line 13: | ||
async function deriveKey(pin, salt) { | async function deriveKey(pin, salt) { | ||
var enc = new TextEncoder(); | var enc = new TextEncoder(); | ||
var baseKey = await crypto.subtle.importKey( | var baseKey = await crypto.subtle.importKey("raw", enc.encode(pin), "PBKDF2", false, ["deriveKey"]); | ||
return crypto.subtle.deriveKey( | return crypto.subtle.deriveKey( | ||
{ name: "PBKDF2", salt: salt, iterations: 200000, hash: "SHA-256" }, | { name: "PBKDF2", salt: salt, iterations: 200000, hash: "SHA-256" }, | ||
| Line 30: | Line 24: | ||
function renderHtml(out, html) { | function renderHtml(out, html) { | ||
while (out.firstChild) out.removeChild(out.firstChild); | while (out.firstChild) out.removeChild(out.firstChild); | ||
var frame = document.createElement("iframe"); | var frame = document.createElement("iframe"); | ||
frame.id = "family-trip-frame"; | frame.id = "family-trip-frame"; | ||
frame.title = "Trip to Berlin 2026"; | frame.title = "Trip to Berlin 2026"; | ||
frame.style.cssText = "width:100%;min-height: | frame.style.cssText = "width:100%;min-height:80vh;border:0;border-radius:12px;box-shadow:0 12px 40px rgba(15,23,42,.18);background:#fff;"; | ||
frame.setAttribute("sandbox", "allow-same-origin allow-popups allow-popups-to-escape-sandbox"); | frame.setAttribute("sandbox", "allow-same-origin allow-popups allow-popups-to-escape-sandbox"); | ||
| Line 44: | Line 35: | ||
"<base href=\"https://bogza.ro/\">" + | "<base href=\"https://bogza.ro/\">" + | ||
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">" + | "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">" + | ||
"<link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">" + | |||
"<link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>" + | |||
"<link href=\"https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,600;9..144,700&family=Source+Sans+3:wght@400;600;700&display=swap\" rel=\"stylesheet\">" + | |||
"<link rel=\"stylesheet\" href=\"https://bogza.ro/load.php?lang=en&modules=site.styles%7Cskins.vector.styles.legacy&only=styles&skin=vector\">" + | "<link rel=\"stylesheet\" href=\"https://bogza.ro/load.php?lang=en&modules=site.styles%7Cskins.vector.styles.legacy&only=styles&skin=vector\">" + | ||
"<style>body{margin:16px;background:# | "<style>" + | ||
"</head><body class=\" | ":root{--ink:#1a2332;--muted:#5b6b7c;--line:#d7dee7;--card:#ffffff;--bg:#eef3f8;--brand:#1f6aa5;--ok:#1b7f4e;--soft:#f7fafc;}" + | ||
"html,body{margin:0;padding:0;background:var(--bg);color:var(--ink);font-family:'Source Sans 3',Segoe UI,sans-serif;line-height:1.55;}" + | |||
"body{padding:20px 16px 48px;}" + | |||
".trip-shell{max-width:980px;margin:0 auto;}" + | |||
".hero{background:linear-gradient(135deg,#1f6aa5 0%,#2f8f6b 55%,#f0b429 130%);color:#fff;border-radius:18px;padding:22px 24px;margin:0 0 18px;box-shadow:0 16px 40px rgba(31,106,165,.28);}" + | |||
".hero h1{font-family:Fraunces,Georgia,serif;font-size:1.7rem;line-height:1.2;margin:0 0 8px;font-weight:700;}" + | |||
".hero p{margin:0;opacity:.95;font-size:1.02rem;}" + | |||
".mw-parser-output,.mw-content-ltr{background:transparent;}" + | |||
".mw-parser-output > .mw-heading1:first-child{display:none;}" + | |||
"h1,h2,h3,h4{font-family:Fraunces,Georgia,serif;color:#16324a;line-height:1.25;}" + | |||
"h2{border-bottom:2px solid #d9e7f2;padding-bottom:.25em;margin-top:1.6em;}" + | |||
"a{color:var(--brand);}" + | |||
"table{border-collapse:separate;border-spacing:0;width:100%;margin:1em 0;background:var(--card);border:1px solid var(--line);border-radius:12px;overflow:hidden;box-shadow:0 6px 18px rgba(15,23,42,.06);}" + | |||
"th,td{border-bottom:1px solid var(--line);border-right:1px solid var(--line);padding:10px 12px;vertical-align:top;}" + | |||
"tr > :last-child{border-right:0;}" + | |||
"tr:last-child > *{border-bottom:0;}" + | |||
"th{background:#e9f2fa;font-weight:700;color:#184866;}" + | |||
".wikitable{background:var(--card);}" + | |||
"img{max-width:100%;height:auto;border-radius:10px;}" + | |||
"ul,ol{padding-left:1.25em;}" + | |||
"pre,code{font-family:ui-monospace,Consolas,monospace;}" + | |||
"pre{background:#0f172a;color:#e2e8f0;border-radius:10px;padding:12px;overflow:auto;}" + | |||
".gallery{display:flex;flex-wrap:wrap;gap:10px;}" + | |||
".gallerybox{background:var(--card);border:1px solid var(--line);border-radius:12px;padding:8px;box-shadow:0 4px 14px rgba(15,23,42,.05);}" + | |||
".thumbinner{border:0 !important;background:transparent !important;padding:0 !important;}" + | |||
"</style></head><body><div class=\"trip-shell\">" + | |||
"<div class=\"hero\"><h1>Trip to Berlin 2026</h1><p>Family road journal · PIN unlocked · Welcome home to Iași</p></div>" + | |||
html + | html + | ||
"</body></html>"; | "</div></body></html>"; | ||
frame.srcdoc = doc; | frame.srcdoc = doc; | ||
out.appendChild(frame); | out.appendChild(frame); | ||
frame.addEventListener("load", function () { | frame.addEventListener("load", function () { | ||
try { | try { | ||
var h = frame.contentDocument.documentElement.scrollHeight; | var h = frame.contentDocument.documentElement.scrollHeight; | ||
if (h && h > 400) frame.style.height = Math.min(h + | if (h && h > 400) frame.style.height = Math.min(h + 48, 16000) + "px"; | ||
} catch (e) {} | } catch (e) {} | ||
}); | }); | ||
| Line 65: | Line 84: | ||
if (!el || el.getAttribute("data-ready") === "1") return; | if (!el || el.getAttribute("data-ready") === "1") return; | ||
el.setAttribute("data-ready", "1"); | el.setAttribute("data-ready", "1"); | ||
var pack; | var pack; | ||
try { | try { pack = JSON.parse(el.textContent); } catch (e) { return; } | ||
var gate = document.createElement("div"); | var gate = document.createElement("div"); | ||
gate.id = "family-trip-gate-v3"; | gate.id = "family-trip-gate-v3"; | ||
gate.style.cssText = | gate.style.cssText = "max-width:560px;margin:2em auto;padding:1.5em 1.6em;border:0;background:linear-gradient(180deg,#ffffff,#f3f7fb);border-radius:16px;font-family:Segoe UI,sans-serif;box-shadow:0 18px 40px rgba(15,23,42,.12);"; | ||
gate.innerHTML = | gate.innerHTML = | ||
"<h2 style=\"margin | "<div style=\"font-size:2rem;margin-bottom:6px\">🏠🔒</div>" + | ||
"<p>Enter the family PIN to open the | "<h2 style=\"margin:0 0 8px;color:#16324a\">Family trip journal</h2>" + | ||
"<label for=\"family-trip-pin-v3\">PIN</label><br>" + | "<p style=\"color:#5b6b7c;margin:0 0 14px\">PIN-protected memories from Iași → Berlin → home. Enter the family PIN to open the finished itinerary.</p>" + | ||
"<label for=\"family-trip-pin-v3\" style=\"font-weight:600;color:#184866\">Family PIN</label><br>" + | |||
"<input id=\"family-trip-pin-v3\" type=\"password\" inputmode=\"numeric\" autocomplete=\"off\" " + | "<input id=\"family-trip-pin-v3\" type=\"password\" inputmode=\"numeric\" autocomplete=\"off\" " + | ||
"style=\"font-size:1. | "style=\"font-size:1.25em;letter-spacing:0.25em;padding:10px 12px;width:11em;margin:10px 0;border:1px solid #c5d3e0;border-radius:10px\" /> " + | ||
"<button id=\"family-trip-unlock-v3\" type=\"button\" style=\"padding: | "<button id=\"family-trip-unlock-v3\" type=\"button\" style=\"padding:10px 16px;border:0;border-radius:999px;background:#1f6aa5;color:#fff;font-weight:700;cursor:pointer\">Unlock</button>" + | ||
"<div id=\"family-trip-err-v3\" style=\"color:#b32424;margin-top: | "<div id=\"family-trip-err-v3\" style=\"color:#b32424;margin-top:10px;min-height:1.2em\"></div>"; | ||
var out = document.createElement("div"); | var out = document.createElement("div"); | ||
out.id = "family-trip-out-v3"; | out.id = "family-trip-out-v3"; | ||
out.style.cssText = "display:none;margin | out.style.cssText = "display:none;margin:1em 0 2em;"; | ||
el.style.display = "none"; | el.style.display = "none"; | ||
| Line 99: | Line 113: | ||
err.textContent = ""; | err.textContent = ""; | ||
var pin = document.getElementById("family-trip-pin-v3").value.trim(); | var pin = document.getElementById("family-trip-pin-v3").value.trim(); | ||
if (pin.length < 4) { | if (pin.length < 4) { err.textContent = "PIN too short."; return; } | ||
btn.disabled = true; | btn.disabled = true; | ||
err.textContent = "Unlocking…"; | err.textContent = "Unlocking…"; | ||
| Line 114: | Line 125: | ||
var html = new TextDecoder().decode(pt); | var html = new TextDecoder().decode(pt); | ||
if (pack.fmt !== "html") { | if (pack.fmt !== "html") { | ||
err.textContent = "Unexpected package format | err.textContent = "Unexpected package format."; | ||
btn.disabled = false; | btn.disabled = false; | ||
return; | return; | ||
} | } | ||
if (html.indexOf("<table") !== -1 && html.indexOf("<table") === -1) { | if (html.indexOf("<table") !== -1 && html.indexOf("<table") === -1) { | ||
var ta = document.createElement("textarea"); | var ta = document.createElement("textarea"); | ||
| Line 139: | Line 149: | ||
} | } | ||
if (window.mw && mw.hook) | if (window.mw && mw.hook) mw.hook("wikipage.content").add(function () { mount(); }); | ||
else document.addEventListener("DOMContentLoaded", mount); | |||
})(); | })(); | ||
/* === FAMILY_TRIP_PIN_GATE_END === */ | /* === FAMILY_TRIP_PIN_GATE_END === */ | ||
Revision as of 23:26, 14 August 2026
/* Any JavaScript here will be loaded for all users on every page load. */
/* === FAMILY_TRIP_PIN_GATE_START === */
(function () {
var CIPHER_ID = "family-trip-cipher-v3";
function b64ToBytes(b64) {
var bin = atob(b64);
var out = new Uint8Array(bin.length);
for (var i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
return out;
}
async function deriveKey(pin, salt) {
var enc = new TextEncoder();
var baseKey = await crypto.subtle.importKey("raw", enc.encode(pin), "PBKDF2", false, ["deriveKey"]);
return crypto.subtle.deriveKey(
{ name: "PBKDF2", salt: salt, iterations: 200000, hash: "SHA-256" },
baseKey,
{ name: "AES-GCM", length: 256 },
false,
["decrypt"]
);
}
function renderHtml(out, html) {
while (out.firstChild) out.removeChild(out.firstChild);
var frame = document.createElement("iframe");
frame.id = "family-trip-frame";
frame.title = "Trip to Berlin 2026";
frame.style.cssText = "width:100%;min-height:80vh;border:0;border-radius:12px;box-shadow:0 12px 40px rgba(15,23,42,.18);background:#fff;";
frame.setAttribute("sandbox", "allow-same-origin allow-popups allow-popups-to-escape-sandbox");
var doc =
"<!DOCTYPE html><html><head><meta charset=\"utf-8\">" +
"<base href=\"https://bogza.ro/\">" +
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">" +
"<link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">" +
"<link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>" +
"<link href=\"https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,600;9..144,700&family=Source+Sans+3:wght@400;600;700&display=swap\" rel=\"stylesheet\">" +
"<link rel=\"stylesheet\" href=\"https://bogza.ro/load.php?lang=en&modules=site.styles%7Cskins.vector.styles.legacy&only=styles&skin=vector\">" +
"<style>" +
":root{--ink:#1a2332;--muted:#5b6b7c;--line:#d7dee7;--card:#ffffff;--bg:#eef3f8;--brand:#1f6aa5;--ok:#1b7f4e;--soft:#f7fafc;}" +
"html,body{margin:0;padding:0;background:var(--bg);color:var(--ink);font-family:'Source Sans 3',Segoe UI,sans-serif;line-height:1.55;}" +
"body{padding:20px 16px 48px;}" +
".trip-shell{max-width:980px;margin:0 auto;}" +
".hero{background:linear-gradient(135deg,#1f6aa5 0%,#2f8f6b 55%,#f0b429 130%);color:#fff;border-radius:18px;padding:22px 24px;margin:0 0 18px;box-shadow:0 16px 40px rgba(31,106,165,.28);}" +
".hero h1{font-family:Fraunces,Georgia,serif;font-size:1.7rem;line-height:1.2;margin:0 0 8px;font-weight:700;}" +
".hero p{margin:0;opacity:.95;font-size:1.02rem;}" +
".mw-parser-output,.mw-content-ltr{background:transparent;}" +
".mw-parser-output > .mw-heading1:first-child{display:none;}" +
"h1,h2,h3,h4{font-family:Fraunces,Georgia,serif;color:#16324a;line-height:1.25;}" +
"h2{border-bottom:2px solid #d9e7f2;padding-bottom:.25em;margin-top:1.6em;}" +
"a{color:var(--brand);}" +
"table{border-collapse:separate;border-spacing:0;width:100%;margin:1em 0;background:var(--card);border:1px solid var(--line);border-radius:12px;overflow:hidden;box-shadow:0 6px 18px rgba(15,23,42,.06);}" +
"th,td{border-bottom:1px solid var(--line);border-right:1px solid var(--line);padding:10px 12px;vertical-align:top;}" +
"tr > :last-child{border-right:0;}" +
"tr:last-child > *{border-bottom:0;}" +
"th{background:#e9f2fa;font-weight:700;color:#184866;}" +
".wikitable{background:var(--card);}" +
"img{max-width:100%;height:auto;border-radius:10px;}" +
"ul,ol{padding-left:1.25em;}" +
"pre,code{font-family:ui-monospace,Consolas,monospace;}" +
"pre{background:#0f172a;color:#e2e8f0;border-radius:10px;padding:12px;overflow:auto;}" +
".gallery{display:flex;flex-wrap:wrap;gap:10px;}" +
".gallerybox{background:var(--card);border:1px solid var(--line);border-radius:12px;padding:8px;box-shadow:0 4px 14px rgba(15,23,42,.05);}" +
".thumbinner{border:0 !important;background:transparent !important;padding:0 !important;}" +
"</style></head><body><div class=\"trip-shell\">" +
"<div class=\"hero\"><h1>Trip to Berlin 2026</h1><p>Family road journal · PIN unlocked · Welcome home to Iași</p></div>" +
html +
"</div></body></html>";
frame.srcdoc = doc;
out.appendChild(frame);
frame.addEventListener("load", function () {
try {
var h = frame.contentDocument.documentElement.scrollHeight;
if (h && h > 400) frame.style.height = Math.min(h + 48, 16000) + "px";
} catch (e) {}
});
}
function mount() {
var el = document.getElementById(CIPHER_ID);
if (!el || el.getAttribute("data-ready") === "1") return;
el.setAttribute("data-ready", "1");
var pack;
try { pack = JSON.parse(el.textContent); } catch (e) { return; }
var gate = document.createElement("div");
gate.id = "family-trip-gate-v3";
gate.style.cssText = "max-width:560px;margin:2em auto;padding:1.5em 1.6em;border:0;background:linear-gradient(180deg,#ffffff,#f3f7fb);border-radius:16px;font-family:Segoe UI,sans-serif;box-shadow:0 18px 40px rgba(15,23,42,.12);";
gate.innerHTML =
"<div style=\"font-size:2rem;margin-bottom:6px\">🏠🔒</div>" +
"<h2 style=\"margin:0 0 8px;color:#16324a\">Family trip journal</h2>" +
"<p style=\"color:#5b6b7c;margin:0 0 14px\">PIN-protected memories from Iași → Berlin → home. Enter the family PIN to open the finished itinerary.</p>" +
"<label for=\"family-trip-pin-v3\" style=\"font-weight:600;color:#184866\">Family PIN</label><br>" +
"<input id=\"family-trip-pin-v3\" type=\"password\" inputmode=\"numeric\" autocomplete=\"off\" " +
"style=\"font-size:1.25em;letter-spacing:0.25em;padding:10px 12px;width:11em;margin:10px 0;border:1px solid #c5d3e0;border-radius:10px\" /> " +
"<button id=\"family-trip-unlock-v3\" type=\"button\" style=\"padding:10px 16px;border:0;border-radius:999px;background:#1f6aa5;color:#fff;font-weight:700;cursor:pointer\">Unlock</button>" +
"<div id=\"family-trip-err-v3\" style=\"color:#b32424;margin-top:10px;min-height:1.2em\"></div>";
var out = document.createElement("div");
out.id = "family-trip-out-v3";
out.style.cssText = "display:none;margin:1em 0 2em;";
el.style.display = "none";
el.parentNode.insertBefore(gate, el);
el.parentNode.insertBefore(out, el.nextSibling);
async function unlock() {
var err = document.getElementById("family-trip-err-v3");
var btn = document.getElementById("family-trip-unlock-v3");
err.textContent = "";
var pin = document.getElementById("family-trip-pin-v3").value.trim();
if (pin.length < 4) { err.textContent = "PIN too short."; return; }
btn.disabled = true;
err.textContent = "Unlocking…";
try {
var key = await deriveKey(pin, b64ToBytes(pack.salt));
var pt = await crypto.subtle.decrypt(
{ name: "AES-GCM", iv: b64ToBytes(pack.nonce), tagLength: 128 },
key,
b64ToBytes(pack.ciphertext)
);
var html = new TextDecoder().decode(pt);
if (pack.fmt !== "html") {
err.textContent = "Unexpected package format.";
btn.disabled = false;
return;
}
if (html.indexOf("<table") !== -1 && html.indexOf("<table") === -1) {
var ta = document.createElement("textarea");
ta.innerHTML = html;
html = ta.value;
}
renderHtml(out, html);
out.style.display = "block";
gate.style.display = "none";
} catch (e) {
err.textContent = "Wrong PIN.";
btn.disabled = false;
}
}
document.getElementById("family-trip-unlock-v3").addEventListener("click", unlock);
document.getElementById("family-trip-pin-v3").addEventListener("keydown", function (ev) {
if (ev.key === "Enter") unlock();
});
}
if (window.mw && mw.hook) mw.hook("wikipage.content").add(function () { mount(); });
else document.addEventListener("DOMContentLoaded", mount);
})();
/* === FAMILY_TRIP_PIN_GATE_END === */