<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://interfaces3.hunter.cryptograss.live/index.php?action=history&amp;feed=atom&amp;title=User%3AMagent%2FCommon.js_proposal</id>
	<title>User:Magent/Common.js proposal - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://interfaces3.hunter.cryptograss.live/index.php?action=history&amp;feed=atom&amp;title=User%3AMagent%2FCommon.js_proposal"/>
	<link rel="alternate" type="text/html" href="https://interfaces3.hunter.cryptograss.live/index.php?title=User:Magent/Common.js_proposal&amp;action=history"/>
	<updated>2026-11-15T06:48:13Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.43.6</generator>
	<entry>
		<id>https://interfaces3.hunter.cryptograss.live/index.php?title=User:Magent/Common.js_proposal&amp;diff=8907&amp;oldid=prev</id>
		<title>Magent: Superseded by the extension; point at the step-2 file</title>
		<link rel="alternate" type="text/html" href="https://interfaces3.hunter.cryptograss.live/index.php?title=User:Magent/Common.js_proposal&amp;diff=8907&amp;oldid=prev"/>
		<updated>2026-09-21T23:20:20Z</updated>

		<summary type="html">&lt;p&gt;Superseded by the extension; point at the step-2 file&lt;/p&gt;
&lt;a href=&quot;https://interfaces3.hunter.cryptograss.live/index.php?title=User:Magent/Common.js_proposal&amp;amp;diff=8907&amp;amp;oldid=8722&quot;&gt;Show changes&lt;/a&gt;</summary>
		<author><name>Magent</name></author>
	</entry>
	<entry>
		<id>https://interfaces3.hunter.cryptograss.live/index.php?title=User:Magent/Common.js_proposal&amp;diff=8722&amp;oldid=prev</id>
		<title>Magent: Quality selector (Auto / 1080p / 720p / 360p / Audio only), and prefer hls.js over native HLS so the ladder is visible</title>
		<link rel="alternate" type="text/html" href="https://interfaces3.hunter.cryptograss.live/index.php?title=User:Magent/Common.js_proposal&amp;diff=8722&amp;oldid=prev"/>
		<updated>2026-09-17T17:39:16Z</updated>

		<summary type="html">&lt;p&gt;Quality selector (Auto / 1080p / 720p / 360p / Audio only), and prefer hls.js over native HLS so the ladder is visible&lt;/p&gt;
&lt;a href=&quot;https://interfaces3.hunter.cryptograss.live/index.php?title=User:Magent/Common.js_proposal&amp;amp;diff=8722&amp;amp;oldid=7935&quot;&gt;Show changes&lt;/a&gt;</summary>
		<author><name>Magent</name></author>
	</entry>
	<entry>
		<id>https://interfaces3.hunter.cryptograss.live/index.php?title=User:Magent/Common.js_proposal&amp;diff=7935&amp;oldid=prev</id>
		<title>Magent: Exact source of the proposed MediaWiki:Common.js (pickipedia#111), meant to be copied from ?action=raw (via create-page on MediaWiki MCP Server)</title>
		<link rel="alternate" type="text/html" href="https://interfaces3.hunter.cryptograss.live/index.php?title=User:Magent/Common.js_proposal&amp;diff=7935&amp;oldid=prev"/>
		<updated>2026-09-15T01:23:11Z</updated>

		<summary type="html">&lt;p&gt;Exact source of the proposed MediaWiki:Common.js (pickipedia#111), meant to be copied from ?action=raw (via create-page on MediaWiki MCP Server)&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;/**&lt;br /&gt;
* Blue Railroad Submission - Date to Block Height Converter&lt;br /&gt;
* Adds a datepicker that converts dates to Ethereum block heights&lt;br /&gt;
* Also shows time ago for news templates&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
(function() {&lt;br /&gt;
    &amp;#039;use strict&amp;#039;;&lt;br /&gt;
    &lt;br /&gt;
    // Only run on the Blue Railroad Submission form&lt;br /&gt;
    if (!document.querySelector(&amp;#039;input[name=&amp;quot;Blue Railroad Submission[block_height]&amp;quot;]&amp;#039;)) {&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    // Reference point: known block and timestamp&lt;br /&gt;
    // Post-merge average block time is ~12.12 seconds&lt;br /&gt;
    const AVG_BLOCK_TIME = 12.12;&lt;br /&gt;
    &lt;br /&gt;
    // Get current block from footer (format: &amp;quot;24,328,442&amp;quot;)&lt;br /&gt;
    function getCurrentBlockFromFooter() {&lt;br /&gt;
        const footerLink = document.querySelector(&amp;#039;a[href*=&amp;quot;etherscan.io/block/&amp;quot;]&amp;#039;);&lt;br /&gt;
        if (footerLink) {&lt;br /&gt;
            const match = footerLink.href.match(/block\/(\d+)/);&lt;br /&gt;
            if (match) return parseInt(match[1]);&lt;br /&gt;
        }&lt;br /&gt;
        return null;&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    // Calculate block height from date&lt;br /&gt;
    function dateToBlockHeight(targetDate, refBlock, refTimestamp) {&lt;br /&gt;
        const targetTimestamp = targetDate.getTime() / 1000;&lt;br /&gt;
        const secondsDiff = refTimestamp - targetTimestamp;&lt;br /&gt;
        const blocksDiff = Math.round(secondsDiff / AVG_BLOCK_TIME);&lt;br /&gt;
        return refBlock - blocksDiff;&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    // Add the datepicker UI&lt;br /&gt;
    function addDatePicker() {&lt;br /&gt;
        const blockInput = document.querySelector(&amp;#039;input[name=&amp;quot;Blue Railroad Submission[block_height]&amp;quot;]&amp;#039;);&lt;br /&gt;
        if (!blockInput) return;&lt;br /&gt;
        &lt;br /&gt;
        const container = document.createElement(&amp;#039;div&amp;#039;);&lt;br /&gt;
        container.style.marginTop = &amp;#039;8px&amp;#039;;&lt;br /&gt;
        container.innerHTML = `&lt;br /&gt;
            &amp;lt;label style=&amp;quot;display: block; margin-bottom: 4px; font-size: 0.9em;&amp;quot;&amp;gt;&lt;br /&gt;
                Or pick a date/time:&lt;br /&gt;
            &amp;lt;/label&amp;gt;&lt;br /&gt;
            &amp;lt;input type=&amp;quot;datetime-local&amp;quot; id=&amp;quot;br-datepicker&amp;quot; style=&amp;quot;padding: 4px; margin-right: 8px;&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;button type=&amp;quot;button&amp;quot; id=&amp;quot;br-convert-btn&amp;quot; style=&amp;quot;padding: 4px 12px; cursor: pointer;&amp;quot;&amp;gt;&lt;br /&gt;
                Convert to Block&lt;br /&gt;
            &amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;span id=&amp;quot;br-status&amp;quot; style=&amp;quot;margin-left: 8px; font-size: 0.9em; color: #666;&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
        `;&lt;br /&gt;
        &lt;br /&gt;
        blockInput.parentNode.appendChild(container);&lt;br /&gt;
        &lt;br /&gt;
        const datePicker = document.getElementById(&amp;#039;br-datepicker&amp;#039;);&lt;br /&gt;
        const convertBtn = document.getElementById(&amp;#039;br-convert-btn&amp;#039;);&lt;br /&gt;
        const status = document.getElementById(&amp;#039;br-status&amp;#039;);&lt;br /&gt;
        &lt;br /&gt;
        // Set default to now&lt;br /&gt;
        const now = new Date();&lt;br /&gt;
        datePicker.value = now.toISOString().slice(0, 16);&lt;br /&gt;
        &lt;br /&gt;
        convertBtn.addEventListener(&amp;#039;click&amp;#039;, function() {&lt;br /&gt;
            const selectedDate = new Date(datePicker.value);&lt;br /&gt;
            if (isNaN(selectedDate.getTime())) {&lt;br /&gt;
                status.textContent = &amp;#039;Invalid date&amp;#039;;&lt;br /&gt;
                status.style.color = &amp;#039;red&amp;#039;;&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            &lt;br /&gt;
            const currentBlock = getCurrentBlockFromFooter();&lt;br /&gt;
            if (!currentBlock) {&lt;br /&gt;
                status.textContent = &amp;#039;Could not find reference block&amp;#039;;&lt;br /&gt;
                status.style.color = &amp;#039;red&amp;#039;;&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            &lt;br /&gt;
            const currentTimestamp = Date.now() / 1000;&lt;br /&gt;
            const estimatedBlock = dateToBlockHeight(selectedDate, currentBlock, currentTimestamp);&lt;br /&gt;
            &lt;br /&gt;
            if (estimatedBlock &amp;gt; currentBlock) {&lt;br /&gt;
                status.textContent = &amp;#039;Date is in the future!&amp;#039;;&lt;br /&gt;
                status.style.color = &amp;#039;orange&amp;#039;;&lt;br /&gt;
            } else {&lt;br /&gt;
                status.textContent = &amp;#039;~estimated&amp;#039;;&lt;br /&gt;
                status.style.color = &amp;#039;green&amp;#039;;&lt;br /&gt;
            }&lt;br /&gt;
            &lt;br /&gt;
            blockInput.value = estimatedBlock;&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    // Run when DOM is ready&lt;br /&gt;
    if (document.readyState === &amp;#039;loading&amp;#039;) {&lt;br /&gt;
        document.addEventListener(&amp;#039;DOMContentLoaded&amp;#039;, addDatePicker);&lt;br /&gt;
    } else {&lt;br /&gt;
        addDatePicker();&lt;br /&gt;
    }&lt;br /&gt;
})();&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * TimeAgo — shows &amp;quot;X blocks ago&amp;quot; for transcluded templates.&lt;br /&gt;
 *&lt;br /&gt;
 * Usage in wikitext:&lt;br /&gt;
 *   &amp;lt;span class=&amp;quot;timeago&amp;quot; data-lastmod-page=&amp;quot;Template:NewsShorts&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
 *&lt;br /&gt;
 * Fetches the last revision timestamp from the API and renders&lt;br /&gt;
 * a block-based relative time string using Ethereum block heights.&lt;br /&gt;
 */&lt;br /&gt;
(function() {&lt;br /&gt;
    &amp;#039;use strict&amp;#039;;&lt;br /&gt;
&lt;br /&gt;
    // Post-merge average block time is ~12.12 seconds&lt;br /&gt;
    var AVG_BLOCK_TIME = 12.12;&lt;br /&gt;
&lt;br /&gt;
    var spans = document.querySelectorAll(&amp;#039;.timeago[data-lastmod-page]&amp;#039;);&lt;br /&gt;
    if (!spans.length) return;&lt;br /&gt;
&lt;br /&gt;
    // Format number with commas (e.g., 1234567 -&amp;gt; &amp;quot;1,234,567&amp;quot;)&lt;br /&gt;
    function formatNumber(num) {&lt;br /&gt;
        return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, &amp;#039;,&amp;#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Collect unique page titles&lt;br /&gt;
    var titles = [];&lt;br /&gt;
    spans.forEach(function(span) {&lt;br /&gt;
        var t = span.getAttribute(&amp;#039;data-lastmod-page&amp;#039;);&lt;br /&gt;
        if (t &amp;amp;&amp;amp; titles.indexOf(t) === -1) titles.push(t);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    // Batch query the API (up to 50 titles per request)&lt;br /&gt;
    var api = mw.config.get(&amp;#039;wgScriptPath&amp;#039;) + &amp;#039;/api.php&amp;#039;;&lt;br /&gt;
    var url = api + &amp;#039;?action=query&amp;amp;prop=revisions&amp;amp;rvprop=timestamp&amp;amp;format=json&amp;amp;titles=&amp;#039; +&lt;br /&gt;
        encodeURIComponent(titles.join(&amp;#039;|&amp;#039;));&lt;br /&gt;
&lt;br /&gt;
    fetch(url).then(function(r) { return r.json(); }).then(function(data) {&lt;br /&gt;
        var pages = data.query &amp;amp;&amp;amp; data.query.pages || {};&lt;br /&gt;
        var timestamps = {};&lt;br /&gt;
&lt;br /&gt;
        Object.keys(pages).forEach(function(id) {&lt;br /&gt;
            var page = pages[id];&lt;br /&gt;
            if (page.revisions &amp;amp;&amp;amp; page.revisions[0]) {&lt;br /&gt;
                timestamps[page.title] = new Date(page.revisions[0].timestamp);&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        spans.forEach(function(span) {&lt;br /&gt;
            var title = span.getAttribute(&amp;#039;data-lastmod-page&amp;#039;);&lt;br /&gt;
            var ts = timestamps[title];&lt;br /&gt;
            if (!ts) {&lt;br /&gt;
                span.textContent = &amp;#039;&amp;#039;;&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            span.textContent = formatBlocksAgo(ts);&lt;br /&gt;
            span.title = ts.toLocaleString();&lt;br /&gt;
        });&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    function formatBlocksAgo(date) {&lt;br /&gt;
        var secondsAgo = Math.floor((Date.now() - date.getTime()) / 1000);&lt;br /&gt;
        var blocksAgo = Math.round(secondsAgo / AVG_BLOCK_TIME);&lt;br /&gt;
&lt;br /&gt;
        if (blocksAgo &amp;lt; 1) {&lt;br /&gt;
            return &amp;#039;this block&amp;#039;;&lt;br /&gt;
        } else if (blocksAgo === 1) {&lt;br /&gt;
            return &amp;#039;1 block ago&amp;#039;;&lt;br /&gt;
        } else {&lt;br /&gt;
            return formatNumber(blocksAgo) + &amp;#039; blocks ago&amp;#039;;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
})();&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * HLS Video Player - Initializes HLS.js for IPFS-hosted videos&lt;br /&gt;
 *&lt;br /&gt;
 * Usage in wikitext (via Template:HLSVideo):&lt;br /&gt;
 *   &amp;lt;div class=&amp;quot;hls-video-player&amp;quot; data-cid=&amp;quot;QmXet6...&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
 *&lt;br /&gt;
 * The gadget loads hls.js and initializes players for any element&lt;br /&gt;
 * with the hls-video-player class and a data-cid attribute.&lt;br /&gt;
 *&lt;br /&gt;
 * Supports both HLS streams (CID/master.m3u8) and raw video files.&lt;br /&gt;
 * Tries HLS first; if the manifest 404s, falls back to direct playback.&lt;br /&gt;
 */&lt;br /&gt;
(function() {&lt;br /&gt;
    &amp;#039;use strict&amp;#039;;&lt;br /&gt;
&lt;br /&gt;
    var IPFS_GATEWAY = &amp;#039;https://ipfs.delivery-kid.cryptograss.live/ipfs&amp;#039;;&lt;br /&gt;
    var HLS_JS_URL = &amp;#039;https://cdn.jsdelivr.net/npm/hls.js@latest&amp;#039;;&lt;br /&gt;
    var hlsLoadPromise = null;&lt;br /&gt;
&lt;br /&gt;
    // CIDv1 (bafy...) is Base32 lowercase, but MediaWiki capitalizes page titles.&lt;br /&gt;
    // CIDv0 (Qm...) is Base58 case-sensitive — must not be lowercased.&lt;br /&gt;
    function normalizeCid(cid) {&lt;br /&gt;
        if (cid &amp;amp;&amp;amp; cid.substring(0, 4) === &amp;#039;Bafy&amp;#039;) {&lt;br /&gt;
            return cid.toLowerCase();&lt;br /&gt;
        }&lt;br /&gt;
        return cid;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function loadHls() {&lt;br /&gt;
        if (typeof Hls !== &amp;#039;undefined&amp;#039;) {&lt;br /&gt;
            return Promise.resolve();&lt;br /&gt;
        }&lt;br /&gt;
        if (hlsLoadPromise) {&lt;br /&gt;
            return hlsLoadPromise;&lt;br /&gt;
        }&lt;br /&gt;
        hlsLoadPromise = new Promise(function(resolve) {&lt;br /&gt;
            var script = document.createElement(&amp;#039;script&amp;#039;);&lt;br /&gt;
            script.src = HLS_JS_URL;&lt;br /&gt;
            script.onload = resolve;&lt;br /&gt;
            document.head.appendChild(script);&lt;br /&gt;
        });&lt;br /&gt;
        return hlsLoadPromise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
// Accepts raw seconds (872), &amp;quot;14:32&amp;quot;, or &amp;quot;1:02:03&amp;quot;. Anything unparseable&lt;br /&gt;
    // means &amp;quot;start at the beginning&amp;quot; rather than throwing — a typo in a&lt;br /&gt;
    // citation should still play the video.&lt;br /&gt;
    function parseTimecode(raw) {&lt;br /&gt;
        if (!raw) { return 0; }&lt;br /&gt;
        var parts = String(raw).trim().split(&amp;#039;:&amp;#039;);&lt;br /&gt;
        for (var i = 0; i &amp;lt; parts.length; i++) {&lt;br /&gt;
            if (parts[i] === &amp;#039;&amp;#039; || isNaN(Number(parts[i]))) { return 0; }&lt;br /&gt;
        }&lt;br /&gt;
        return parts.reduce(function (acc, p) { return acc * 60 + Number(p); }, 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Seek once the browser knows how long the video is.&lt;br /&gt;
    //&lt;br /&gt;
    // All three playback paths below — Safari&amp;#039;s native HLS, hls.js, and the&lt;br /&gt;
    // direct-file fallback — end up attached to a real &amp;lt;video&amp;gt;, so hanging one&lt;br /&gt;
    // listener on the element covers every case. Seeking earlier than&lt;br /&gt;
    // loadedmetadata is unreliable: with hls.js the manifest may not be parsed&lt;br /&gt;
    // yet and the seek is silently dropped.&lt;br /&gt;
    function seekWhenReady(video, seconds) {&lt;br /&gt;
        if (!seconds) { return; }&lt;br /&gt;
        video.addEventListener(&amp;#039;loadedmetadata&amp;#039;, function onMeta() {&lt;br /&gt;
            video.removeEventListener(&amp;#039;loadedmetadata&amp;#039;, onMeta);&lt;br /&gt;
            // A citation pointing past the end should land on the last frame&lt;br /&gt;
            // rather than being ignored outright.&lt;br /&gt;
            video.currentTime = isFinite(video.duration)&lt;br /&gt;
                ? Math.min(seconds, video.duration)&lt;br /&gt;
                : seconds;&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // A #t= fragment in the URL seeks the player too, which is what makes a&lt;br /&gt;
    // {{Src|video|...|t=14:32}} citation land the reader on the assertion&lt;br /&gt;
    // rather than at 0:00 of a forty-minute set.&lt;br /&gt;
    function timecodeFromHash() {&lt;br /&gt;
        var m = /[#&amp;amp;]t=([0-9:]+)/.exec(window.location.hash || &amp;#039;&amp;#039;);&lt;br /&gt;
        return m ? parseTimecode(m[1]) : 0;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function initPlayers() {&lt;br /&gt;
        var containers = document.querySelectorAll(&amp;#039;.hls-video-player[data-cid]:not([data-initialized])&amp;#039;);&lt;br /&gt;
        if (!containers.length) return;&lt;br /&gt;
&lt;br /&gt;
        // Mark ALL containers immediately to prevent race conditions&lt;br /&gt;
        containers.forEach(function(c) {&lt;br /&gt;
            c.setAttribute(&amp;#039;data-initialized&amp;#039;, &amp;#039;true&amp;#039;);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        // Then load hls.js and initialize&lt;br /&gt;
        loadHls().then(function() {&lt;br /&gt;
            containers.forEach(initPlayer);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createVideoElement(width, maxWidth) {&lt;br /&gt;
        var video = document.createElement(&amp;#039;video&amp;#039;);&lt;br /&gt;
        video.controls = true;&lt;br /&gt;
        video.playsInline = true;&lt;br /&gt;
        video.style.width = width;&lt;br /&gt;
        video.style.maxWidth = maxWidth;&lt;br /&gt;
        video.style.backgroundColor = &amp;#039;#000&amp;#039;;&lt;br /&gt;
        video.style.display = &amp;#039;block&amp;#039;;&lt;br /&gt;
        return video;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Can this browser actually decode AV1? Apple ships no software AV1&lt;br /&gt;
    // decoder, so Safari plays it only on M3/A17-and-later hardware; Chrome&lt;br /&gt;
    // and Firefox bundle dav1d and manage it anywhere. Everything published&lt;br /&gt;
    // through delivery-kid since Sept 2026 is AV1, so &amp;quot;can&amp;#039;t play&amp;quot; is far&lt;br /&gt;
    // more often a decoder gap than a missing file.&lt;br /&gt;
    function canDecodeAV1() {&lt;br /&gt;
        var type = &amp;#039;video/mp4; codecs=&amp;quot;av01.0.05M.08&amp;quot;&amp;#039;;&lt;br /&gt;
        try {&lt;br /&gt;
            if (window.MediaSource &amp;amp;&amp;amp; window.MediaSource.isTypeSupported) {&lt;br /&gt;
                return window.MediaSource.isTypeSupported(type);&lt;br /&gt;
            }&lt;br /&gt;
            return document.createElement(&amp;#039;video&amp;#039;).canPlayType(type) !== &amp;#039;&amp;#039;;&lt;br /&gt;
        } catch (e) {&lt;br /&gt;
            return false;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // One honest failure message. The old text blamed IPFS unconditionally,&lt;br /&gt;
    // which sent people debugging storage for what was usually a codec&lt;br /&gt;
    // problem — and said &amp;quot;may not be available&amp;quot; about content that was&lt;br /&gt;
    // demonstrably being served.&lt;br /&gt;
    function showPlaybackError(container, cid) {&lt;br /&gt;
        var url = IPFS_GATEWAY + &amp;#039;/&amp;#039; + normalizeCid(cid) + &amp;#039;/master.m3u8&amp;#039;;&lt;br /&gt;
        var html;&lt;br /&gt;
        if (!canDecodeAV1()) {&lt;br /&gt;
            html = &amp;#039;&amp;lt;p style=&amp;quot;padding:1em; border:1px solid #ac6600; background:#fef6e7;&amp;quot;&amp;gt;&amp;#039;&lt;br /&gt;
                 + &amp;#039;&amp;lt;strong&amp;gt;This browser cannot play this video.&amp;lt;/strong&amp;gt;&amp;lt;br&amp;gt;&amp;#039;&lt;br /&gt;
                 + &amp;#039;It is encoded in AV1 — a royalty-free format, which is why we use it. &amp;#039;&lt;br /&gt;
                 + &amp;#039;Apple ships no software AV1 decoder, so Safari plays it only on M3 or newer Macs &amp;#039;&lt;br /&gt;
                 + &amp;#039;and iPhone 15 Pro or newer.&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&amp;#039;&lt;br /&gt;
                 + &amp;#039;&amp;lt;strong&amp;gt;Firefox and Chrome play it on any desktop&amp;lt;/strong&amp;gt;, including older Intel Macs. &amp;#039;&lt;br /&gt;
                 + &amp;#039;Or download it and play it in VLC.&amp;lt;br&amp;gt;&amp;#039;&lt;br /&gt;
                 + &amp;#039;&amp;lt;a href=&amp;quot;&amp;#039; + url + &amp;#039;&amp;quot; rel=&amp;quot;nofollow&amp;quot;&amp;gt;&amp;#039; + url + &amp;#039;&amp;lt;/a&amp;gt;&amp;#039;&lt;br /&gt;
                 + &amp;#039;&amp;lt;/p&amp;gt;&amp;#039;;&lt;br /&gt;
        } else {&lt;br /&gt;
            html = &amp;#039;&amp;lt;p style=&amp;quot;padding:1em; border:1px solid #b32424; background:#fee;&amp;quot;&amp;gt;&amp;#039;&lt;br /&gt;
                 + &amp;#039;&amp;lt;strong&amp;gt;Could not load this video.&amp;lt;/strong&amp;gt;&amp;lt;br&amp;gt;&amp;#039;&lt;br /&gt;
                 + &amp;#039;Your browser can decode AV1, so this is more likely a fetch or gateway problem &amp;#039;&lt;br /&gt;
                 + &amp;#039;than a codec one. Try the stream directly — if this link works, the content is &amp;#039;&lt;br /&gt;
                 + &amp;#039;present and the fault is in playback rather than availability:&amp;lt;br&amp;gt;&amp;#039;&lt;br /&gt;
                 + &amp;#039;&amp;lt;a href=&amp;quot;&amp;#039; + url + &amp;#039;&amp;quot; rel=&amp;quot;nofollow&amp;quot;&amp;gt;&amp;#039; + url + &amp;#039;&amp;lt;/a&amp;gt;&amp;#039;&lt;br /&gt;
                 + &amp;#039;&amp;lt;/p&amp;gt;&amp;#039;;&lt;br /&gt;
        }&lt;br /&gt;
        container.innerHTML = html;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
function fallbackToDirectVideo(container, cid, width, maxWidth, startSeconds) {&lt;br /&gt;
        // CID is a raw video file, not an HLS stream — play directly&lt;br /&gt;
        var directUrl = IPFS_GATEWAY + &amp;#039;/&amp;#039; + normalizeCid(cid);&lt;br /&gt;
        container.innerHTML = &amp;#039;&amp;#039;;&lt;br /&gt;
        var video = createVideoElement(width, maxWidth);&lt;br /&gt;
        video.src = directUrl;&lt;br /&gt;
        video.addEventListener(&amp;#039;error&amp;#039;, function() {&lt;br /&gt;
            showPlaybackError(container, cid);&lt;br /&gt;
        });&lt;br /&gt;
        seekWhenReady(video, startSeconds);&lt;br /&gt;
        container.appendChild(video);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
function initPlayer(container) {&lt;br /&gt;
        var cid = container.getAttribute(&amp;#039;data-cid&amp;#039;);&lt;br /&gt;
        var normalCid = normalizeCid(cid);&lt;br /&gt;
        var width = container.getAttribute(&amp;#039;data-width&amp;#039;) || &amp;#039;100%&amp;#039;;&lt;br /&gt;
        var maxWidth = container.getAttribute(&amp;#039;data-max-width&amp;#039;) || &amp;#039;800px&amp;#039;;&lt;br /&gt;
&lt;br /&gt;
        // data-start wins over the URL fragment: an embed that explicitly asks&lt;br /&gt;
        // for a moment means it, whereas the fragment is a property of how the&lt;br /&gt;
        // reader arrived and should not override the page&amp;#039;s own intent.&lt;br /&gt;
        var startSeconds = parseTimecode(container.getAttribute(&amp;#039;data-start&amp;#039;))&lt;br /&gt;
            || timecodeFromHash();&lt;br /&gt;
&lt;br /&gt;
        var hlsSrc = IPFS_GATEWAY + &amp;#039;/&amp;#039; + normalCid + &amp;#039;/master.m3u8&amp;#039;;&lt;br /&gt;
&lt;br /&gt;
        var video = createVideoElement(width, maxWidth);&lt;br /&gt;
&lt;br /&gt;
        // A still from the video, pinned alongside it under the same CID, so&lt;br /&gt;
        // the player shows the thing rather than a black rectangle before&lt;br /&gt;
        // anyone presses play. It is a JPEG, which means it also renders on&lt;br /&gt;
        // browsers that cannot decode the AV1 behind it — a Safari user&lt;br /&gt;
        // without hardware AV1 at least sees what they are being offered.&lt;br /&gt;
        //&lt;br /&gt;
        // Releases pinned before posters existed simply have no poster.jpg.&lt;br /&gt;
        // A video element whose poster 404s falls back to its normal empty&lt;br /&gt;
        // state rather than showing a broken image, so this is safe to set&lt;br /&gt;
        // unconditionally.&lt;br /&gt;
        video.poster = IPFS_GATEWAY + &amp;#039;/&amp;#039; + normalCid + &amp;#039;/poster.jpg&amp;#039;;&lt;br /&gt;
        seekWhenReady(video, startSeconds);&lt;br /&gt;
        container.appendChild(video);&lt;br /&gt;
&lt;br /&gt;
        if (video.canPlayType(&amp;#039;application/vnd.apple.mpegurl&amp;#039;)) {&lt;br /&gt;
            // Safari native HLS — try HLS first, fall back on error.&lt;br /&gt;
            // If the browser has no AV1 decoder there is no point chasing the&lt;br /&gt;
            // CID as a raw file: it is an HLS directory, that request fails&lt;br /&gt;
            // too, and the reader is told the content is missing when the&lt;br /&gt;
            // real answer is &amp;quot;this browser cannot decode it&amp;quot;.&lt;br /&gt;
            video.src = hlsSrc;&lt;br /&gt;
            video.addEventListener(&amp;#039;error&amp;#039;, function() {&lt;br /&gt;
                if (!canDecodeAV1()) {&lt;br /&gt;
                    showPlaybackError(container, cid);&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
                fallbackToDirectVideo(container, cid, width, maxWidth, startSeconds);&lt;br /&gt;
            });&lt;br /&gt;
        } else if (typeof Hls !== &amp;#039;undefined&amp;#039; &amp;amp;&amp;amp; Hls.isSupported()) {&lt;br /&gt;
            var hls = new Hls();&lt;br /&gt;
            hls.loadSource(hlsSrc);&lt;br /&gt;
            hls.attachMedia(video);&lt;br /&gt;
            hls.on(Hls.Events.ERROR, function(event, data) {&lt;br /&gt;
                if (data.fatal) {&lt;br /&gt;
                    hls.destroy();&lt;br /&gt;
                    // A media-type fatal on a browser without AV1 is a decoder&lt;br /&gt;
                    // gap, not a missing playlist. Say so rather than falling&lt;br /&gt;
                    // through to a direct-video attempt that cannot succeed.&lt;br /&gt;
                    var mediaFatal = data.type === Hls.ErrorTypes.MEDIA_ERROR;&lt;br /&gt;
                    if (mediaFatal &amp;amp;&amp;amp; !canDecodeAV1()) {&lt;br /&gt;
                        showPlaybackError(container, cid);&lt;br /&gt;
                        return;&lt;br /&gt;
                    }&lt;br /&gt;
                    // Otherwise the usual cause is that this CID is a plain&lt;br /&gt;
                    // video file rather than an HLS directory.&lt;br /&gt;
                    fallbackToDirectVideo(container, cid, width, maxWidth, startSeconds);&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
        } else {&lt;br /&gt;
            // No HLS support — try direct video&lt;br /&gt;
            fallbackToDirectVideo(container, cid, width, maxWidth, startSeconds);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &amp;#039;loading&amp;#039;) {&lt;br /&gt;
        document.addEventListener(&amp;#039;DOMContentLoaded&amp;#039;, initPlayers);&lt;br /&gt;
    } else {&lt;br /&gt;
        initPlayers();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (typeof mw !== &amp;#039;undefined&amp;#039; &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
        mw.hook(&amp;#039;wikipage.content&amp;#039;).add(function($content) {&lt;br /&gt;
            initPlayers();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
})();&lt;br /&gt;
/**&lt;br /&gt;
 * Instrument form — validation + page name computation (v24772700)&lt;br /&gt;
 */&lt;br /&gt;
(function() {&lt;br /&gt;
    &amp;#039;use strict&amp;#039;;&lt;br /&gt;
    console.log(&amp;#039;PickiPedia Common.js instrument module v24772700 loaded&amp;#039;);&lt;br /&gt;
&lt;br /&gt;
    mw.hook(&amp;#039;pf.formValidation&amp;#039;).add(function(args) {&lt;br /&gt;
        var nickname = document.querySelector(&amp;#039;input[name=&amp;quot;Instrument[nickname]&amp;quot;]&amp;#039;);&lt;br /&gt;
        if (!nickname) return;&lt;br /&gt;
&lt;br /&gt;
        var make = document.querySelector(&amp;#039;input[name=&amp;quot;Instrument[make]&amp;quot;]&amp;#039;);&lt;br /&gt;
        var model = document.querySelector(&amp;#039;input[name=&amp;quot;Instrument[model]&amp;quot;]&amp;#039;);&lt;br /&gt;
        var serial = document.querySelector(&amp;#039;input[name=&amp;quot;Instrument[serial]&amp;quot;]&amp;#039;);&lt;br /&gt;
&lt;br /&gt;
        var nickVal = nickname.value.trim();&lt;br /&gt;
        var makeVal = make ? make.value.trim() : &amp;#039;&amp;#039;;&lt;br /&gt;
        var modelVal = model ? model.value.trim() : &amp;#039;&amp;#039;;&lt;br /&gt;
        var serialVal = serial ? serial.value.trim() : &amp;#039;&amp;#039;;&lt;br /&gt;
&lt;br /&gt;
        var hasNickname = nickVal !== &amp;#039;&amp;#039;;&lt;br /&gt;
        var hasMakerComplete = makeVal !== &amp;#039;&amp;#039; &amp;amp;&amp;amp; modelVal !== &amp;#039;&amp;#039; &amp;amp;&amp;amp; serialVal !== &amp;#039;&amp;#039;;&lt;br /&gt;
&lt;br /&gt;
        if (!hasNickname &amp;amp;&amp;amp; !hasMakerComplete) {&lt;br /&gt;
            args.numErrors += 1;&lt;br /&gt;
            if (makeVal !== &amp;#039;&amp;#039; || modelVal !== &amp;#039;&amp;#039; || serialVal !== &amp;#039;&amp;#039;) {&lt;br /&gt;
                alert(&amp;#039;Maker identification requires all three: make, model, and serial number. Or provide a nickname instead.&amp;#039;);&lt;br /&gt;
            } else {&lt;br /&gt;
                alert(&amp;#039;Please provide either a nickname or maker identification (make, model, and serial number).&amp;#039;);&lt;br /&gt;
            }&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // If no nickname, fill it from make/model/serial for page naming&lt;br /&gt;
        if (!hasNickname) {&lt;br /&gt;
            nickname.value = makeVal + &amp;#039; &amp;#039; + modelVal + &amp;#039; &amp;#039; + serialVal;&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
})();&lt;br /&gt;
&lt;br /&gt;
/* ============================================================&lt;br /&gt;
 * Pickipedia — User-subpage zine/Substack styling&lt;br /&gt;
 *&lt;br /&gt;
 * Layout:&lt;br /&gt;
 *   - corner logo (absolute-positioned upper-left of viewport,&lt;br /&gt;
 *     OUTSIDE all wiki wrappers — does not push content down)&lt;br /&gt;
 *   - non-sticky topbar (just nav) at top of body&lt;br /&gt;
 *   - hero block with title (top), then USER:Username kicker below,&lt;br /&gt;
 *     optional dek + meta&lt;br /&gt;
 *   - light/dark mode toggle (persisted in localStorage)&lt;br /&gt;
 *&lt;br /&gt;
 * Variant from HTML comment marker `pickipedia:variant=letter|masthead`&lt;br /&gt;
 * or category membership; default letter.&lt;br /&gt;
 * ============================================================ */&lt;br /&gt;
&lt;br /&gt;
(function () {&lt;br /&gt;
  &amp;#039;use strict&amp;#039;;&lt;br /&gt;
&lt;br /&gt;
  if (typeof mw === &amp;#039;undefined&amp;#039; || !mw.config) return;&lt;br /&gt;
&lt;br /&gt;
  var nsNumber = mw.config.get(&amp;#039;wgNamespaceNumber&amp;#039;);&lt;br /&gt;
  var pageName = mw.config.get(&amp;#039;wgPageName&amp;#039;) || &amp;#039;&amp;#039;;&lt;br /&gt;
  var title    = mw.config.get(&amp;#039;wgTitle&amp;#039;) || &amp;#039;&amp;#039;;&lt;br /&gt;
  var action   = mw.config.get(&amp;#039;wgAction&amp;#039;) || &amp;#039;view&amp;#039;;&lt;br /&gt;
&lt;br /&gt;
  if (nsNumber !== 2) return;&lt;br /&gt;
  if (action !== &amp;#039;view&amp;#039;) return;&lt;br /&gt;
  if (title.indexOf(&amp;#039;/&amp;#039;) === -1) return;&lt;br /&gt;
&lt;br /&gt;
  var slash    = title.indexOf(&amp;#039;/&amp;#039;);&lt;br /&gt;
  var username = title.slice(0, slash).replace(/_/g, &amp;#039; &amp;#039;);&lt;br /&gt;
  var subpage  = title.slice(slash + 1).replace(/_/g, &amp;#039; &amp;#039;);&lt;br /&gt;
&lt;br /&gt;
  var body = document.body;&lt;br /&gt;
  body.classList.add(&amp;#039;pickipedia-userpost&amp;#039;);&lt;br /&gt;
&lt;br /&gt;
  var variant = &amp;#039;letter&amp;#039;;&lt;br /&gt;
  var content = document.querySelector(&amp;#039;.mw-parser-output&amp;#039;);&lt;br /&gt;
  if (content) {&lt;br /&gt;
    var html = content.innerHTML || &amp;#039;&amp;#039;;&lt;br /&gt;
    var m = html.match(/pickipedia:variant\s*=\s*(letter|masthead)/i);&lt;br /&gt;
    if (m) variant = m[1].toLowerCase();&lt;br /&gt;
  }&lt;br /&gt;
  var cats = mw.config.get(&amp;#039;wgCategories&amp;#039;) || [];&lt;br /&gt;
  if (cats.indexOf(&amp;#039;Masthead style&amp;#039;) !== -1) variant = &amp;#039;masthead&amp;#039;;&lt;br /&gt;
  if (cats.indexOf(&amp;#039;Letter style&amp;#039;) !== -1) variant = &amp;#039;letter&amp;#039;;&lt;br /&gt;
  body.classList.add(&amp;#039;pickipedia-variant-&amp;#039; + variant);&lt;br /&gt;
&lt;br /&gt;
  var STORAGE_KEY = &amp;#039;pickipedia-mode&amp;#039;;&lt;br /&gt;
  var saved = null;&lt;br /&gt;
  try { saved = localStorage.getItem(STORAGE_KEY); } catch (e) {}&lt;br /&gt;
  if (saved === &amp;#039;dark&amp;#039;) body.classList.add(&amp;#039;pickipedia-dark&amp;#039;);&lt;br /&gt;
&lt;br /&gt;
  var LOGO_URL = &amp;#039;/images/thumb/8/80/Pickipedia-quarter-transparent.png/400px-Pickipedia-quarter-transparent.png&amp;#039;;&lt;br /&gt;
&lt;br /&gt;
  // ---------- Corner logo: stand-alone body child, absolute-positioned. ----------&lt;br /&gt;
  var cornerLogo = document.createElement(&amp;#039;a&amp;#039;);&lt;br /&gt;
  cornerLogo.className = &amp;#039;pp-corner-logo&amp;#039;;&lt;br /&gt;
  cornerLogo.href = &amp;#039;/wiki/Main_Page&amp;#039;;&lt;br /&gt;
  cornerLogo.title = &amp;#039;PickiPedia home&amp;#039;;&lt;br /&gt;
  cornerLogo.innerHTML = &amp;#039;&amp;lt;img src=&amp;quot;&amp;#039; + LOGO_URL + &amp;#039;&amp;quot; alt=&amp;quot;PickiPedia&amp;quot;&amp;gt;&amp;#039;;&lt;br /&gt;
  document.body.insertBefore(cornerLogo, document.body.firstChild);&lt;br /&gt;
&lt;br /&gt;
  // ---------- Topbar (nav only) ----------&lt;br /&gt;
  var topbar = document.createElement(&amp;#039;header&amp;#039;);&lt;br /&gt;
  topbar.className = &amp;#039;pickipedia-topbar&amp;#039;;&lt;br /&gt;
  topbar.innerHTML =&lt;br /&gt;
    &amp;#039;&amp;lt;nav class=&amp;quot;pickipedia-topbar-nav&amp;quot;&amp;gt;&amp;#039; +&lt;br /&gt;
      &amp;#039;&amp;lt;a href=&amp;quot;&amp;#039; + mw.util.getUrl(pageName, { action: &amp;#039;edit&amp;#039; }) + &amp;#039;&amp;quot;&amp;gt;edit&amp;lt;/a&amp;gt;&amp;#039; +&lt;br /&gt;
      &amp;#039;&amp;lt;a href=&amp;quot;&amp;#039; + mw.util.getUrl(pageName, { action: &amp;#039;history&amp;#039; }) + &amp;#039;&amp;quot;&amp;gt;history&amp;lt;/a&amp;gt;&amp;#039; +&lt;br /&gt;
      &amp;#039;&amp;lt;a href=&amp;quot;&amp;#039; + mw.util.getUrl(&amp;#039;Talk:&amp;#039; + pageName) + &amp;#039;&amp;quot;&amp;gt;talk&amp;lt;/a&amp;gt;&amp;#039; +&lt;br /&gt;
    &amp;#039;&amp;lt;/nav&amp;gt;&amp;#039;;&lt;br /&gt;
  document.body.insertBefore(topbar, cornerLogo.nextSibling);&lt;br /&gt;
&lt;br /&gt;
  // ---------- Hero ----------&lt;br /&gt;
  if (content) {&lt;br /&gt;
    var dek = &amp;#039;&amp;#039;;&lt;br /&gt;
    var dekEl = content.querySelector(&amp;#039;.pp-dek-source&amp;#039;);&lt;br /&gt;
    if (dekEl) {&lt;br /&gt;
      dek = dekEl.innerHTML;&lt;br /&gt;
      dekEl.remove();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var kickerHtml = &amp;#039;&amp;lt;a href=&amp;quot;&amp;#039; + mw.util.getUrl(&amp;#039;User:&amp;#039; + username.replace(/ /g, &amp;#039;_&amp;#039;)) +&lt;br /&gt;
                     &amp;#039;&amp;quot;&amp;gt;User:&amp;#039; + escapeHtml(username) + &amp;#039;&amp;lt;/a&amp;gt;&amp;#039;;&lt;br /&gt;
    var kickerEl = content.querySelector(&amp;#039;.pp-kicker-source&amp;#039;);&lt;br /&gt;
    if (kickerEl) {&lt;br /&gt;
      kickerHtml = escapeHtml(kickerEl.textContent.trim());&lt;br /&gt;
      kickerEl.remove();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var lastMod = &amp;#039;&amp;#039;;&lt;br /&gt;
    var lastModEl = document.getElementById(&amp;#039;footer-info-lastmod&amp;#039;);&lt;br /&gt;
    if (lastModEl) lastMod = lastModEl.textContent.replace(/^\s*This page was last (edited|modified) on\s*/i, &amp;#039;&amp;#039;).trim();&lt;br /&gt;
&lt;br /&gt;
    var hero = document.createElement(&amp;#039;header&amp;#039;);&lt;br /&gt;
    hero.className = &amp;#039;pickipedia-hero&amp;#039;;&lt;br /&gt;
&lt;br /&gt;
    if (variant === &amp;#039;masthead&amp;#039;) {&lt;br /&gt;
      hero.innerHTML =&lt;br /&gt;
        &amp;#039;&amp;lt;div class=&amp;quot;pp-topbar&amp;quot;&amp;gt;&amp;#039; +&lt;br /&gt;
          &amp;#039;&amp;lt;div class=&amp;quot;pp-kicker&amp;quot;&amp;gt;&amp;#039; + kickerHtml + &amp;#039;&amp;lt;/div&amp;gt;&amp;#039; +&lt;br /&gt;
          &amp;#039;&amp;lt;div class=&amp;quot;pp-topbar-rule&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;#039; +&lt;br /&gt;
          (lastMod ? &amp;#039;&amp;lt;div class=&amp;quot;pp-topbar-vol&amp;quot;&amp;gt;&amp;#039; + escapeHtml(lastMod) + &amp;#039;&amp;lt;/div&amp;gt;&amp;#039; : &amp;#039;&amp;#039;) +&lt;br /&gt;
        &amp;#039;&amp;lt;/div&amp;gt;&amp;#039; +&lt;br /&gt;
        &amp;#039;&amp;lt;div class=&amp;quot;pp-titlewrap&amp;quot;&amp;gt;&amp;#039; +&lt;br /&gt;
          &amp;#039;&amp;lt;h1 class=&amp;quot;pp-title&amp;quot;&amp;gt;&amp;#039; + escapeHtml(subpage) + &amp;#039;&amp;lt;/h1&amp;gt;&amp;#039; +&lt;br /&gt;
          (dek ? &amp;#039;&amp;lt;p class=&amp;quot;pp-dek&amp;quot;&amp;gt;&amp;#039; + dek + &amp;#039;&amp;lt;/p&amp;gt;&amp;#039; : &amp;#039;&amp;#039;) +&lt;br /&gt;
        &amp;#039;&amp;lt;/div&amp;gt;&amp;#039;;&lt;br /&gt;
    } else {&lt;br /&gt;
      // Letter variant: title first, then kicker, then optional dek/meta.&lt;br /&gt;
      hero.innerHTML =&lt;br /&gt;
        &amp;#039;&amp;lt;h1 class=&amp;quot;pp-title&amp;quot;&amp;gt;&amp;#039; + escapeHtml(subpage) + &amp;#039;&amp;lt;/h1&amp;gt;&amp;#039; +&lt;br /&gt;
        &amp;#039;&amp;lt;div class=&amp;quot;pp-kicker&amp;quot;&amp;gt;&amp;#039; + kickerHtml + &amp;#039;&amp;lt;/div&amp;gt;&amp;#039; +&lt;br /&gt;
        (dek ? &amp;#039;&amp;lt;p class=&amp;quot;pp-dek&amp;quot;&amp;gt;&amp;#039; + dek + &amp;#039;&amp;lt;/p&amp;gt;&amp;#039; : &amp;#039;&amp;#039;) +&lt;br /&gt;
        (lastMod ? &amp;#039;&amp;lt;div class=&amp;quot;pp-meta&amp;quot;&amp;gt;Last edited &amp;#039; + escapeHtml(lastMod) + &amp;#039;&amp;lt;/div&amp;gt;&amp;#039; : &amp;#039;&amp;#039;);&lt;br /&gt;
    }&lt;br /&gt;
    content.parentNode.insertBefore(hero, content);&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  // ---------- Light/dark toggle ----------&lt;br /&gt;
  var toggle = document.createElement(&amp;#039;button&amp;#039;);&lt;br /&gt;
  toggle.className = &amp;#039;pickipedia-mode&amp;#039;;&lt;br /&gt;
  toggle.type = &amp;#039;button&amp;#039;;&lt;br /&gt;
  toggle.textContent = body.classList.contains(&amp;#039;pickipedia-dark&amp;#039;) ? &amp;#039;☼ Light&amp;#039; : &amp;#039;☾ Dark&amp;#039;;&lt;br /&gt;
  toggle.addEventListener(&amp;#039;click&amp;#039;, function () {&lt;br /&gt;
    var dark = body.classList.toggle(&amp;#039;pickipedia-dark&amp;#039;);&lt;br /&gt;
    toggle.textContent = dark ? &amp;#039;☼ Light&amp;#039; : &amp;#039;☾ Dark&amp;#039;;&lt;br /&gt;
    try { localStorage.setItem(STORAGE_KEY, dark ? &amp;#039;dark&amp;#039; : &amp;#039;light&amp;#039;); } catch (e) {}&lt;br /&gt;
  });&lt;br /&gt;
  document.body.appendChild(toggle);&lt;br /&gt;
&lt;br /&gt;
  function escapeHtml(s) {&lt;br /&gt;
    return String(s == null ? &amp;#039;&amp;#039; : s)&lt;br /&gt;
      .replace(/&amp;amp;/g, &amp;#039;&amp;amp;amp;&amp;#039;)&lt;br /&gt;
      .replace(/&amp;lt;/g, &amp;#039;&amp;amp;lt;&amp;#039;)&lt;br /&gt;
      .replace(/&amp;gt;/g, &amp;#039;&amp;amp;gt;&amp;#039;)&lt;br /&gt;
      .replace(/&amp;quot;/g, &amp;#039;&amp;amp;quot;&amp;#039;);&lt;br /&gt;
    }&lt;br /&gt;
}());&lt;/div&gt;</summary>
		<author><name>Magent</name></author>
	</entry>
</feed>