MediaWiki:Common.js: Difference between revisions
Jump to navigation
Jump to search
m Redesign family trip landing + PIN unlock first page |
m Support modular metadata per trip page (PIN gate v3) |
||
| Line 93: | Line 93: | ||
"#family-trip-out-v3 { margin-top: 1.25rem; }", | "#family-trip-out-v3 { margin-top: 1.25rem; }", | ||
"#family-trip-frame { width: 100%; min-height: 80vh; border: 0; border-radius: 18px;", | "#family-trip-frame { width: 100%; min-height: 80vh; border: 0; border-radius: 18px;", | ||
" box-shadow: 0 18px 48px rgba(15,23,42,.16); background: #fff; }" | " box-shadow: 0 18px 48px rgba(15,23,42,.16); background: #fff; }", | ||
".trip-gallery { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 16px; margin: 1.5em 0; }", | |||
".trip-photo-card { background: #fff; border: 1px solid #d7dee7; border-radius: 16px; overflow: hidden; box-shadow: 0 6px 18px rgba(15,23,42,.06); }", | |||
".trip-photo-card img { width: 100%; height: 240px; object-fit: cover; display: block; cursor: pointer; transition: transform .2s ease; }", | |||
".trip-photo-card img:hover { transform: scale(1.02); }", | |||
".trip-photo-cap { padding: 12px 14px; font-size: .92rem; color: #334155; line-height: 1.4; border-top: 1px solid #f1f5f9; }" | |||
].join("\n"); | ].join("\n"); | ||
document.head.appendChild(style); | document.head.appendChild(style); | ||
} | } | ||
function renderHtml(out, html) { | function renderHtml(out, html, pack) { | ||
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"; | ||
var tripTitle = pack.title || "Family Trip Journal"; | |||
var tripSubtitle = pack.heroSubtitle || "Family road journal · PIN unlocked"; | |||
frame.title = tripTitle; | |||
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"); | ||
var heroHeader = pack.noHeroHeader ? "" : "<div class=\"hero\"><h1>" + tripTitle + "</h1><p>" + tripSubtitle + "</p></div>"; | |||
var doc = | var doc = | ||
"<!DOCTYPE html><html><head><meta charset=\"utf-8\">" + | "<!DOCTYPE html><html><head><meta charset=\"utf-8\">" + | ||
"<base href=\"https://bogza.ro/\">" + | "<base href=\"https://bogza.ro/\">" + | ||
"<meta name= | "<meta name="viewport" content="width=device-width, initial-scale=1">" + | ||
"<link rel= | "<link rel="preconnect" href="https://fonts.googleapis.com">" + | ||
"<link rel= | "<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>" + | ||
"<link href= | "<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= | "<link rel="stylesheet" href="https://bogza.ro/load.php?lang=en&modules=site.styles%7Cskins.vector.styles.legacy&only=styles&skin=vector">" + | ||
"<style>" + | "<style>" + | ||
":root{--ink:#1a2332;--muted:#5b6b7c;--line:#d7dee7;--card:#ffffff;--bg:#eef3f8;--brand:#1f6aa5;}" + | ":root{--ink:#1a2332;--muted:#5b6b7c;--line:#d7dee7;--card:#ffffff;--bg:#eef3f8;--brand:#1f6aa5;}" + | ||
| Line 136: | Line 145: | ||
"pre,code{font-family:ui-monospace,Consolas,monospace;}" + | "pre,code{font-family:ui-monospace,Consolas,monospace;}" + | ||
"pre{background:#0f172a;color:#e2e8f0;border-radius:10px;padding:12px;overflow:auto;}" + | "pre{background:#0f172a;color:#e2e8f0;border-radius:10px;padding:12px;overflow:auto;}" + | ||
".trip-gallery { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 16px; margin: 1.5em 0; }" + | |||
".trip-photo-card { background: #fff; border: 1px solid #d7dee7; border-radius: 16px; overflow: hidden; box-shadow: 0 6px 18px rgba(15,23,42,.06); }" + | |||
".trip-photo-card img { width: 100%; height: 260px; object-fit: cover; display: block; border-radius: 16px 16px 0 0; }" + | |||
".trip-photo-cap { padding: 12px 14px; font-size: .92rem; color: #334155; line-height: 1.4; border-top: 1px solid #f1f5f9; }" + | |||
".gallery{display:flex;flex-wrap:wrap;gap:10px;}" + | ".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);}" + | ".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;}" + | ".thumbinner{border:0 !important;background:transparent !important;padding:0 !important;}" + | ||
"</style></head><body><div class= | "</style></head><body><div class="trip-shell">" + | ||
heroHeader + | |||
html + | html + | ||
"</div></body></html>"; | "</div></body></html>"; | ||
| Line 165: | Line 178: | ||
var fallback = document.getElementById("family-trip-landing-fallback"); | var fallback = document.getElementById("family-trip-landing-fallback"); | ||
if (fallback) fallback.style.display = "none"; | if (fallback) fallback.style.display = "none"; | ||
var kickerText = pack.kicker || "Family journal · locked"; | |||
var titleText = pack.title || "Welcome home from Berlin"; | |||
var leadText = pack.lead || "Fourteen days on the road, memories sealed with a family PIN. Unlock to reopen the itinerary, kids checklists, and photo trail."; | |||
var routeHtml = ""; | |||
if (pack.chips && Array.isArray(pack.chips)) { | |||
routeHtml = "<div class=\"ft-route\">" + pack.chips.map(function(c) { | |||
return "<div class=\"ft-chip\">" + c + "</div>"; | |||
}).join("") + "</div>"; | |||
} else { | |||
routeHtml = | |||
"<div class="ft-route">" + | |||
"<div class="ft-chip">Iași <span>→</span> Cluj</div>" + | |||
"<div class="ft-chip">Budapest <span>→</span> Vienna</div>" + | |||
"<div class="ft-chip">LEGOLAND <span>→</span> Berlin</div>" + | |||
"<div class="ft-chip">Kraków <span>→</span> Oradea <span>→</span> Home</div>" + | |||
"</div>"; | |||
} | |||
var statsHtml = ""; | |||
if (pack.stats && Array.isArray(pack.stats)) { | |||
statsHtml = "<div class=\"ft-meta\">" + pack.stats.map(function(s) { | |||
return "<div class=\"ft-stat\"><b>" + s.val + "</b><span>" + s.label + "</span></div>"; | |||
}).join("") + "</div>"; | |||
} else { | |||
statsHtml = | |||
"<div class="ft-meta">" + | |||
"<div class="ft-stat"><b>3–14 Aug</b><span>Main travel days</span></div>" + | |||
"<div class="ft-stat"><b>6 countries</b><span>RO · HU · AT · DE · PL · SK</span></div>" + | |||
"<div class="ft-stat"><b>Trip complete</b><span>Back in Iași</span></div>" + | |||
"</div>"; | |||
} | |||
var gate = document.createElement("div"); | var gate = document.createElement("div"); | ||
| Line 170: | Line 216: | ||
gate.className = "family-trip-landing-shell"; | gate.className = "family-trip-landing-shell"; | ||
gate.innerHTML = | gate.innerHTML = | ||
"<div class= | "<div class="family-trip-landing-inner">" + | ||
"<div class= | "<div class="ft-kicker">" + kickerText + "</div>" + | ||
"<h2 class="ft-title">" + titleText + "</h2>" + | |||
"<p class="ft-lead">" + leadText + "</p>" + | |||
routeHtml + | |||
"< | statsHtml + | ||
"<div class="ft-unlock">" + | |||
"<label for="family-trip-pin-v3">Enter the family PIN</label>" + | |||
"< | "<div class="ft-row">" + | ||
"<input id="family-trip-pin-v3" type="password" inputmode="numeric" autocomplete="off" placeholder="••••" />" + | |||
"<button id="family-trip-unlock-v3" type="button">Unlock journal</button>" + | |||
"<div class= | |||
"<label for= | |||
"<div class= | |||
"<input id= | |||
"<button id= | |||
"</div>" + | "</div>" + | ||
"<div id= | "<div id="family-trip-err-v3" class="ft-err"></div>" + | ||
"</div>" + | "</div>" + | ||
"<p class= | "<p class="ft-note">If the unlock card looks old, hard-refresh with Ctrl+F5.</p>" + | ||
"</div>"; | "</div>"; | ||
| Line 232: | Line 269: | ||
html = ta.value; | html = ta.value; | ||
} | } | ||
renderHtml(out, html); | renderHtml(out, html, pack); | ||
out.style.display = "block"; | out.style.display = "block"; | ||
gate.style.display = "none"; | gate.style.display = "none"; | ||
Revision as of 15:52, 30 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 injectLandingStyles() {
if (document.getElementById("family-trip-landing-css")) return;
var style = document.createElement("style");
style.id = "family-trip-landing-css";
style.textContent = [
"#content.mw-body, #bodyContent { background: transparent !important; }",
"#firstHeading { display: none !important; }",
"#siteSub, #contentSub, #jump-to-nav { display: none !important; }",
".family-trip-landing-shell {",
" position: relative; overflow: hidden; margin: 0 auto 1.5rem; max-width: 720px;",
" border-radius: 28px; padding: 2px;",
" background: linear-gradient(135deg, rgba(31,106,165,.55), rgba(47,143,107,.45), rgba(240,180,41,.35));",
" box-shadow: 0 28px 60px rgba(22,50,74,.18);",
"}",
".family-trip-landing-inner {",
" position: relative; border-radius: 26px; overflow: hidden;",
" background:",
" radial-gradient(1200px 420px at 10% -10%, rgba(255,255,255,.55), transparent 55%),",
" radial-gradient(900px 380px at 110% 0%, rgba(240,180,41,.18), transparent 50%),",
" linear-gradient(160deg, #f7fbff 0%, #eef6f2 48%, #f8f4ea 100%);",
" padding: 34px 28px 30px; font-family: 'Segoe UI', system-ui, sans-serif; color: #16324a;",
"}",
".family-trip-landing-inner::before {",
" content: ''; position: absolute; inset: 0; pointer-events: none; opacity: .35;",
" background-image: linear-gradient(rgba(22,50,74,.035) 1px, transparent 1px),",
" linear-gradient(90deg, rgba(22,50,74,.035) 1px, transparent 1px);",
" background-size: 28px 28px; mask-image: linear-gradient(180deg, #000, transparent 90%);",
"}",
".ft-kicker { position: relative; display: inline-flex; align-items: center; gap: 8px;",
" font-size: .78rem; letter-spacing: .14em; text-transform: uppercase; font-weight: 700;",
" color: #1f6aa5; background: rgba(255,255,255,.72); border: 1px solid rgba(31,106,165,.18);",
" border-radius: 999px; padding: 7px 12px; margin-bottom: 16px; }",
".ft-title { position: relative; margin: 0 0 10px; font-size: clamp(1.8rem, 4vw, 2.45rem);",
" line-height: 1.12; font-weight: 750; letter-spacing: -0.02em; color: #12283a; }",
".ft-lead { position: relative; margin: 0 0 22px; max-width: 36rem; color: #4d6275;",
" font-size: 1.05rem; line-height: 1.55; }",
".ft-route { position: relative; display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 22px; }",
".ft-chip { display: inline-flex; align-items: center; gap: 6px; border-radius: 999px;",
" padding: 8px 12px; background: rgba(255,255,255,.8); border: 1px solid rgba(22,50,74,.08);",
" color: #1a3348; font-weight: 650; font-size: .92rem; box-shadow: 0 6px 16px rgba(22,50,74,.05); }",
".ft-chip span { opacity: .55; font-weight: 600; }",
".ft-meta { position: relative; display: grid; grid-template-columns: repeat(3, minmax(0,1fr));",
" gap: 10px; margin: 0 0 24px; }",
".ft-stat { background: rgba(255,255,255,.78); border: 1px solid rgba(22,50,74,.07);",
" border-radius: 16px; padding: 12px 14px; }",
".ft-stat b { display: block; font-size: 1.02rem; color: #143248; margin-bottom: 2px; }",
".ft-stat span { color: #62768a; font-size: .86rem; }",
".ft-unlock { position: relative; background: rgba(255,255,255,.88);",
" border: 1px solid rgba(31,106,165,.14); border-radius: 20px; padding: 18px 18px 16px;",
" box-shadow: inset 0 1px 0 rgba(255,255,255,.8), 0 12px 28px rgba(31,106,165,.08); }",
".ft-unlock label { display: block; font-weight: 700; color: #184866; margin-bottom: 8px; }",
".ft-row { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }",
".ft-row input { flex: 1 1 160px; min-width: 140px; font-size: 1.2rem; letter-spacing: .28em;",
" padding: 12px 14px; border-radius: 14px; border: 1px solid #c5d3e0; background: #fff;",
" color: #16324a; outline: none; transition: border-color .15s, box-shadow .15s; }",
".ft-row input:focus { border-color: #1f6aa5; box-shadow: 0 0 0 4px rgba(31,106,165,.15); }",
".ft-row button { appearance: none; border: 0; cursor: pointer; border-radius: 999px;",
" padding: 12px 18px; font-weight: 750; color: #fff;",
" background: linear-gradient(135deg, #1f6aa5, #2f8f6b);",
" box-shadow: 0 10px 22px rgba(31,106,165,.28); transition: transform .12s, filter .12s; }",
".ft-row button:hover { filter: brightness(1.05); transform: translateY(-1px); }",
".ft-row button:disabled { opacity: .7; cursor: wait; transform: none; }",
".ft-err { min-height: 1.25em; margin-top: 10px; color: #b32424; font-size: .95rem; }",
".ft-note { position: relative; margin: 16px 0 0; color: #6a7d90; font-size: .88rem; }",
"@media (max-width: 640px) {",
" .family-trip-landing-inner { padding: 26px 18px 22px; }",
" .ft-meta { grid-template-columns: 1fr; }",
"}",
"#family-trip-out-v3 { margin-top: 1.25rem; }",
"#family-trip-frame { width: 100%; min-height: 80vh; border: 0; border-radius: 18px;",
" box-shadow: 0 18px 48px rgba(15,23,42,.16); background: #fff; }",
".trip-gallery { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 16px; margin: 1.5em 0; }",
".trip-photo-card { background: #fff; border: 1px solid #d7dee7; border-radius: 16px; overflow: hidden; box-shadow: 0 6px 18px rgba(15,23,42,.06); }",
".trip-photo-card img { width: 100%; height: 240px; object-fit: cover; display: block; cursor: pointer; transition: transform .2s ease; }",
".trip-photo-card img:hover { transform: scale(1.02); }",
".trip-photo-cap { padding: 12px 14px; font-size: .92rem; color: #334155; line-height: 1.4; border-top: 1px solid #f1f5f9; }"
].join("\n");
document.head.appendChild(style);
}
function renderHtml(out, html, pack) {
while (out.firstChild) out.removeChild(out.firstChild);
var frame = document.createElement("iframe");
frame.id = "family-trip-frame";
var tripTitle = pack.title || "Family Trip Journal";
var tripSubtitle = pack.heroSubtitle || "Family road journal · PIN unlocked";
frame.title = tripTitle;
frame.setAttribute("sandbox", "allow-same-origin allow-popups allow-popups-to-escape-sandbox");
var heroHeader = pack.noHeroHeader ? "" : "<div class=\"hero\"><h1>" + tripTitle + "</h1><p>" + tripSubtitle + "</p></div>";
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;}" +
"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;}" +
".trip-gallery { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 16px; margin: 1.5em 0; }" +
".trip-photo-card { background: #fff; border: 1px solid #d7dee7; border-radius: 16px; overflow: hidden; box-shadow: 0 6px 18px rgba(15,23,42,.06); }" +
".trip-photo-card img { width: 100%; height: 260px; object-fit: cover; display: block; border-radius: 16px 16px 0 0; }" +
".trip-photo-cap { padding: 12px 14px; font-size: .92rem; color: #334155; line-height: 1.4; border-top: 1px solid #f1f5f9; }" +
".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">" +
heroHeader +
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");
injectLandingStyles();
var pack;
try { pack = JSON.parse(el.textContent); } catch (e) { return; }
var fallback = document.getElementById("family-trip-landing-fallback");
if (fallback) fallback.style.display = "none";
var kickerText = pack.kicker || "Family journal · locked";
var titleText = pack.title || "Welcome home from Berlin";
var leadText = pack.lead || "Fourteen days on the road, memories sealed with a family PIN. Unlock to reopen the itinerary, kids checklists, and photo trail.";
var routeHtml = "";
if (pack.chips && Array.isArray(pack.chips)) {
routeHtml = "<div class=\"ft-route\">" + pack.chips.map(function(c) {
return "<div class=\"ft-chip\">" + c + "</div>";
}).join("") + "</div>";
} else {
routeHtml =
"<div class="ft-route">" +
"<div class="ft-chip">Iași <span>→</span> Cluj</div>" +
"<div class="ft-chip">Budapest <span>→</span> Vienna</div>" +
"<div class="ft-chip">LEGOLAND <span>→</span> Berlin</div>" +
"<div class="ft-chip">Kraków <span>→</span> Oradea <span>→</span> Home</div>" +
"</div>";
}
var statsHtml = "";
if (pack.stats && Array.isArray(pack.stats)) {
statsHtml = "<div class=\"ft-meta\">" + pack.stats.map(function(s) {
return "<div class=\"ft-stat\"><b>" + s.val + "</b><span>" + s.label + "</span></div>";
}).join("") + "</div>";
} else {
statsHtml =
"<div class="ft-meta">" +
"<div class="ft-stat"><b>3–14 Aug</b><span>Main travel days</span></div>" +
"<div class="ft-stat"><b>6 countries</b><span>RO · HU · AT · DE · PL · SK</span></div>" +
"<div class="ft-stat"><b>Trip complete</b><span>Back in Iași</span></div>" +
"</div>";
}
var gate = document.createElement("div");
gate.id = "family-trip-gate-v3";
gate.className = "family-trip-landing-shell";
gate.innerHTML =
"<div class="family-trip-landing-inner">" +
"<div class="ft-kicker">" + kickerText + "</div>" +
"<h2 class="ft-title">" + titleText + "</h2>" +
"<p class="ft-lead">" + leadText + "</p>" +
routeHtml +
statsHtml +
"<div class="ft-unlock">" +
"<label for="family-trip-pin-v3">Enter the family PIN</label>" +
"<div class="ft-row">" +
"<input id="family-trip-pin-v3" type="password" inputmode="numeric" autocomplete="off" placeholder="••••" />" +
"<button id="family-trip-unlock-v3" type="button">Unlock journal</button>" +
"</div>" +
"<div id="family-trip-err-v3" class="ft-err"></div>" +
"</div>" +
"<p class="ft-note">If the unlock card looks old, hard-refresh with Ctrl+F5.</p>" +
"</div>";
var out = document.createElement("div");
out.id = "family-trip-out-v3";
out.style.display = "none";
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 = "Opening the journal…";
err.style.color = "#1f6aa5";
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.style.color = "#b32424";
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, pack);
out.style.display = "block";
gate.style.display = "none";
} catch (e) {
err.style.color = "#b32424";
err.textContent = "Wrong PIN — try again.";
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 === */